From 206e9f86315dc65a1d3614941e10f1605d3f62cb Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 23 Aug 2021 01:25:36 -0400 Subject: [PATCH 001/100] New Requester module nehavior + tests TODO: refactor this into data layers. Revise layout syntax, pre-load, and tests --- esm/data/requester.js | 103 +++++++++++++++++-------------- esm/registry/index.js | 3 +- esm/registry/joins.js | 25 ++++++++ package-lock.json | 5 ++ package.json | 3 +- test/unit/data/test_requester.js | 60 ++++++++++++++++++ webpack.common.cjs | 1 + 7 files changed, 152 insertions(+), 48 deletions(-) create mode 100644 esm/registry/joins.js create mode 100644 test/unit/data/test_requester.js diff --git a/esm/data/requester.js b/esm/data/requester.js index 2e5b5cea..2c546087 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -1,4 +1,19 @@ -import { TRANSFORMS } from '../registry'; +import {getLinkedData} from 'undercomplicate'; + +import { JOINS } from '../registry'; + + +class JoinTask { + constructor(join_type, params) { + this._callable = JOINS.get(join_type); + this._params = params; + } + + getData(options, left, right) { + return Promise.resolve(this._callable(left, right, ...this._params)); + } +} + /** * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers. @@ -17,57 +32,53 @@ class Requester { this._sources = sources; } - __split_requests(fields) { - // Given a fields array, return an object specifying what datasource names the data layer should make requests - // to, and how to handle the returned data - var requests = {}; - // Regular expression finds namespace:field|trans - var re = /^(?:([^:]+):)?([^:|]*)(\|.+)*$/; - fields.forEach(function(raw) { - var parts = re.exec(raw); - var ns = parts[1] || 'base'; - var field = parts[2]; - var trans = TRANSFORMS.get(parts[3]); - if (typeof requests[ns] == 'undefined') { - requests[ns] = {outnames:[], fields:[], trans:[]}; + _config_to_sources(namespace_options, join_options) { + // 1. Find the data sources needed for this request, and add in the joins for this layer + // namespaces: { assoc: assoc, ld(assoc): ld } + // TODO: Move this to the data layer creation step, along with contract validation + // Dependencies are defined as raw data + joins + const dependencies = []; + // Create a list of sources unique to this layer, including both fetch and join operations + const entities = new Map(); + Object.entries(namespace_options) + .forEach(([label, source_name]) => { + // In layout syntax, namespace names and dependencies are written together, like ld = ld(assoc). Convert. + let match = label.match(/^(\w+)$|^(\w+)\(/); + if (!match) { + throw new Error(`Invalid namespace name: '${label}'. Should be 'somename' or 'somename(somedep)'`); + } + label = match[1] || match[2]; + + const source = this._sources.get(source_name); + + if (entities.has(label)) { + throw new Error(`Configuration error: within a layer, namespace name '${label}' must be unique`); + } + + entities.set(label, source); + dependencies.push(label); + }); + + join_options.forEach((config) => { + const {type, name, requires, params} = config; + if (entities.has(name)) { + throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`); } - requests[ns].outnames.push(raw); - requests[ns].fields.push(field); - requests[ns].trans.push(trans); + const task = new JoinTask(type, params); + entities.set(name, task); + dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB) }); - return requests; + return [entities, dependencies]; } - /** - * Fetch data, and create a chain that only connects two data sources if they depend on each other - * @param {Object} state The current "state" of the plot, such as chromosome and start/end positions - * @param {String[]} fields The list of data fields specified in the `layout` for a specific data layer - * @returns {Promise} - */ - getData(state, fields) { - var requests = this.__split_requests(fields); - // Create an array of functions that, when called, will trigger the request to the specified datasource - var request_handles = Object.keys(requests).map((key) => { - if (!this._sources.get(key)) { - throw new Error(`Datasource for namespace ${key} not found`); - } - return this._sources.get(key).getData( - state, - requests[key].fields, - requests[key].outnames, - requests[key].trans - ); - }); - //assume the fields are requested in dependent order - //TODO: better manage dependencies - var ret = Promise.resolve({header:{}, body: [], discrete: {}}); - for (var i = 0; i < request_handles.length; i++) { - // If a single datalayer uses multiple sources, perform the next request when the previous one completes - ret = ret.then(request_handles[i]); - } - return ret; + getData(state, namespace_options, join_options) { + const [entities, dependencies] = this._config_to_sources(namespace_options, join_options); + return getLinkedData(state, entities, dependencies, true); + // entities: { assoc: adapter, ld: adapter } } } export default Requester; + +export {JoinTask as _JoinTask}; diff --git a/esm/registry/index.js b/esm/registry/index.js index 7db80e43..11897896 100644 --- a/esm/registry/index.js +++ b/esm/registry/index.js @@ -4,6 +4,7 @@ import ADAPTERS from './adapters'; import DATA_LAYERS from './data_layers'; import LAYOUTS from './layouts'; +import JOINS from './joins'; import MATCHERS from './matchers'; import SCALABLE from './scalable'; import TRANSFORMS from './transforms'; @@ -13,5 +14,5 @@ export { // Base classes and reusable components ADAPTERS, DATA_LAYERS, LAYOUTS, WIDGETS, // User defined functions for injecting custom behavior into layout directives - MATCHERS, SCALABLE, TRANSFORMS, + JOINS, MATCHERS, SCALABLE, TRANSFORMS, }; diff --git a/esm/registry/joins.js b/esm/registry/joins.js new file mode 100644 index 00000000..dc0e52a3 --- /dev/null +++ b/esm/registry/joins.js @@ -0,0 +1,25 @@ +/** + * "Join" functions + * + * Connect two sets of records together according to predefined rules. + * + * @module LocusZoom_JoinFunctions + */ +import {joins} from 'undercomplicate'; + +import {RegistryBase} from './base'; + +/** + * A plugin registry that allows plots to use both pre-defined and user-provided "data join" functions. + * @alias module:LocusZoom~JoinFunctions + * @type {module:registry/base~RegistryBase} + */ +const registry = new RegistryBase(); + +registry.add('left_match', joins.left_match); + +registry.add('inner_match', joins.inner_match); + +registry.add('full_outer_match', joins.full_outer_match); + +export default registry; diff --git a/package-lock.json b/package-lock.json index df5a0be6..3d62c4bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4248,6 +4248,11 @@ "verror": "1.10.0" } }, + "just-clone": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-3.2.1.tgz", + "integrity": "sha512-PFotEVrrzAnwuWTUOFquDShWrHnUnhxNrVs1VFqkNfnoH3Sn5XUlDOePYn2Vv5cN8xV2y69jf8qEoQHm7eoLnw==" + }, "just-extend": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", diff --git a/package.json b/package.json index 064b89b2..8205abd8 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "docs": "./build_docs.sh" }, "dependencies": { - "d3": "^5.16.0" + "d3": "^5.16.0", + "just-clone": "^3.2.1" }, "devDependencies": { "@babel/core": "^7.13.1", diff --git a/test/unit/data/test_requester.js b/test/unit/data/test_requester.js new file mode 100644 index 00000000..f6d32a5a --- /dev/null +++ b/test/unit/data/test_requester.js @@ -0,0 +1,60 @@ +import {assert} from 'chai'; + +import Requester, {_JoinTask} from '../../../esm/data/requester'; + + +describe('Requester object defines and parses requests', function () { + describe('Layout parsing', function () { + beforeEach(function () { + this._all_datasources = new Map([ + ['assoc1', {name: 'assoc1'}], + ['someld', {name: 'someld'}], + ['assoc2', {name: 'assoc2'}], + ]); + this._requester = new Requester(this._all_datasources); + }); + + it('converts layout configuration entities and dependencies', function () { + // Test name logic + const namespace_options = {'assoc': 'assoc1', 'ld(assoc)': 'someld'}; + const join_options = [{ + type: 'left_match', + name: 'combined', + requires: ['assoc', 'ld'], + params: ['assoc.variant', 'ld.variant'], + }]; + + const [entities, dependencies] = this._requester._config_to_sources(namespace_options, join_options); + + // Validate names of dependencies are correct + assert.deepEqual(dependencies, ['assoc', 'ld', 'combined(assoc, ld)'], 'Dependencies are resolved in expected order'); + + // Validate that correct dependencies were wired up + assert.deepEqual([...entities.keys()], ['assoc', 'ld', 'combined']); + assert.deepEqual(entities.get('assoc'), {name: 'assoc1'}); + assert.deepEqual(entities.get('ld'), {name: 'someld'}); + + assert.instanceOf(entities.get('combined'), _JoinTask, 'A join task was created'); + }); + + it('provides developer friendly error messages', function () { + // Test parse errors: namespaces malformed + assert.throws(() => { + this._requester._config_to_sources({ 'not.allowed': 'whatever' }, {}); + }, /Invalid namespace name: 'not\.allowed'/); + + // Test duplicate namespace errors: assoc + assert.throws(() => { + this._requester._config_to_sources({ 'assoc': {}, 'assoc(dep)': 'this is weird and not supported' }, []); + }, /namespace name 'assoc' must be unique/); + + // Test duplicate namespace errors: joins + assert.throws(() => { + this._requester._config_to_sources( + {}, + [{name: 'combined', type: 'left_match', requires: []}, {name: 'combined', type: 'left_match', requires: []}] + ); + }, /join name 'combined' must be unique/); + }); + }); +}); diff --git a/webpack.common.cjs b/webpack.common.cjs index 0f6836a3..d5d87608 100644 --- a/webpack.common.cjs +++ b/webpack.common.cjs @@ -49,6 +49,7 @@ module.exports = { new ESLintPlugin(), ], resolve: { + symlinks: false, modules: [ 'node_modules', ], From 41b19cdf45c578474fed7e9a5799524a4986a9a9 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 1 Sep 2021 23:31:48 -0400 Subject: [PATCH 002/100] - BREAKING: remove the distinction between abstract/concrete layouts. Namespace specifiers now reference a local view of data: instead of `{{namespace[thing]}}fieldname`, items now reference `thing.fieldname` - Integrate new data retrieval logic and convert existing LZ sources --- esm/components/data_layer/base.js | 4 +- esm/data/adapters.js | 192 +++++++++++++++++++---------- esm/ext/lz-credible-sets.js | 38 +++--- esm/ext/lz-intervals-enrichment.js | 46 +++---- esm/ext/lz-intervals-track.js | 18 +-- esm/layouts/index.js | 125 ++++++++++--------- index.html | 12 +- 7 files changed, 253 insertions(+), 182 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 17ce02f6..484328d1 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -1488,9 +1488,9 @@ class BaseDataLayer { // and then recreated if returning to visibility // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads) - return this.parent_plot.lzd.getData(this.state, this.layout.fields) + return this.parent_plot.lzd.getData(this.state, this.layout.namespace || {}, this.layout.join_options || []) .then((new_data) => { - this.data = new_data.body; // chain.body from datasources + this.data = new_data; this.applyDataMethods(); this.initialized = true; }); diff --git a/esm/data/adapters.js b/esm/data/adapters.js index deca8120..0a2dbd25 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -32,19 +32,139 @@ * @module LocusZoom_Adapters */ -function validateBuildSource(class_name, build, source) { - // Build OR Source, not both - if ((build && source) || !(build || source)) { - throw new Error(`${class_name} must provide a parameter specifying either "build" or "source". It should not specify both.`); +import {BaseUrlAdapter} from 'undercomplicate'; + + +function validateBuildSource() { + // TODO: deleteme +} + +class BaseLZAdapter extends BaseUrlAdapter { + constructor(config) { + super(config); + + this._validate_fields = true; + // Prefix the namespace for this source to all fieldnames: id -> assoc.id + // This is useful for almost all layers because the layout object says where to find every field, exactly. + // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on + // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear + // in the response. (gene_name instead of genes.gene_name) + const {prefix_namespace} = config; + this._prefix_namespace = (typeof prefix_namespace === 'boolean') ? prefix_namespace : true; } - // If the build isn't recognized, our APIs can't transparently select a source to match - if (build && !['GRCh37', 'GRCh38'].includes(build)) { - throw new Error(`${class_name} must specify a valid genome build number`); + + /** + * Note: since namespacing is the last thing we usually want to do, calculations will want to call super AFTER their own code. + * @param records + * @param options + * @returns {*} + * @private + */ + _annotateRecords(records, options) { + if (!this._prefix_namespace) { + return records; + } + + // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for + // assoc data might see "variant" as "assoc.variant" + return records.map((row) => { + return Object.entries(row).reduce( + (acc, [label, value]) => { + acc[`${options._provider_name}.${label}`] = value; + return acc; + }, + {} + ); + }); } } -// NOTE: Custom adapaters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs. +class BaseUMAdapter extends BaseLZAdapter { + constructor(config) { + super(config); + // The UM portaldev API accepts an (optional) parameter "genome_build" + this._genome_build = config.genome_build; + } + + _validateBuildSource(build, source) { + // Build OR Source, not both + if ((build && source) || !(build || source)) { + throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`); + } + // If the build isn't recognized, our APIs can't transparently select a source to match + if (build && !['GRCh37', 'GRCh38'].includes(build)) { + throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`); + } + } + + // Special behavior for the UM portaldev API: col -> row format normalization + _normalizeResponse(response_text, options) { + let data = super._normalizeResponse(...arguments); + // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload + data = data.data || data; + + if (Array.isArray(data)) { + // Already in the desired form + return data; + } + // Otherwise, assume the server response is an object representing columns of data. + // Each array should have the same length (verify), and a given array index corresponds to a single row. + const keys = Object.keys(data); + const N = data[keys[0]].length; + const sameLength = keys.every(function (key) { + const item = data[key]; + return item.length === N; + }); + if (!sameLength) { + throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`); + } + + // Go down the columns, and create an object for each row record + const records = []; + const fields = Object.keys(data); + for (let i = 0; i < N; i++) { + const record = {}; + for (let j = 0; j < fields.length; j++) { + record[fields[j]] = data[fields[j]][i]; + } + records.push(record); + } + return records; + } +} + + +class AssociationLZ extends BaseUMAdapter { + constructor(config) { + // Minimum adapter contract hard-codes fields contract based on UM PortalDev API + // For layers that require more functionality, pass extra_fields to source options + config.fields = ['variant', 'position', 'log_pvalue', 'ref_allele']; + + super(config); + + const { source } = config; + if (!source) { + throw new Error('Association adapter must specify dataset ID via "source" option'); + } + this._source_id = source; + } + + _getURL (state) { + const {chr, start, end} = state; + return `${this._base_url}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`; + } +} + + + + +// class LDServer2 extends BaseUMAdapter { + +// } + + +// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs. // Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal // methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the // private API methods exist in the base class. @@ -418,62 +538,6 @@ class BaseApiAdapter extends BaseAdapter { } } -/** - * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request - * to a specific REST API. - * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter - * @param {string} config.url The base URL for the remote data. - * @param {object} config.params - * @param [config.params.sort=false] Whether to sort the association data (by an assumed field named "position"). This - * is primarily a site-specific workaround for a particular LZ usage; we encourage apis to sort by position before returning - * data to the browser. - * @param [config.params.source] The ID of the GWAS dataset to use for this request, as matching the API backend - */ -class AssociationLZ extends BaseApiAdapter { - preGetData (state, fields, outnames, trans) { - // TODO: Modify internals to see if we can go without this method - const id_field = this.params.id_field || 'id'; - [id_field, 'position'].forEach(function(x) { - if (!fields.includes(x)) { - fields.unshift(x); - outnames.unshift(x); - trans.unshift(null); - } - }); - return {fields: fields, outnames:outnames, trans:trans}; - } - - /** - * Add query parameters to the URL to construct a query for the specified region - */ - getURL (state, chain, fields) { - const analysis = chain.header.analysis || this.params.source || this.params.analysis; // Old usages called this param "analysis" - if (typeof analysis == 'undefined') { - throw new Error('Association source must specify an analysis ID to plot'); - } - return `${this.url}results/?filter=analysis in ${analysis} and chromosome in '${state.chr}' and position ge ${state.start} and position le ${state.end}`; - } - - /** - * Some association sources do not sort their data in a predictable order, which makes it hard to reliably - * align with other sources (such as LD). For performance reasons, sorting is an opt-in argument. - * TODO: Consider more fine grained sorting control in the future. This was added as a very specific - * workaround for the original T2D portal. - * @protected - * @param data - * @return {Object} - */ - normalizeResponse (data) { - data = super.normalizeResponse(data); - if (this.params && this.params.sort && data.length && data[0]['position']) { - data.sort(function (a, b) { - return a['position'] - b['position']; - }); - } - return data; - } -} /** * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant. diff --git a/esm/ext/lz-credible-sets.js b/esm/ext/lz-credible-sets.js index 3ba91ab9..14e4e880 100644 --- a/esm/ext/lz-credible-sets.js +++ b/esm/ext/lz-credible-sets.js @@ -150,7 +150,7 @@ function install (LocusZoom) { const association_credible_set_tooltip = function () { // Extend a known tooltip with an extra row of info showing posterior probabilities const l = LocusZoom.Layouts.get('tooltip', 'standard_association', { unnamespaced: true }); - l.html += '{{#if {{namespace[credset]}}posterior_prob}}
Posterior probability: {{{{namespace[credset]}}posterior_prob|scinotation|htmlescape}}{{/if}}'; + l.html += '{{#if credset.posterior_prob}}
Posterior probability: {{credset.posterior_prob|scinotation|htmlescape}}{{/if}}'; return l; }(); @@ -167,9 +167,9 @@ function install (LocusZoom) { closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: '{{{{namespace[assoc]}}variant|htmlescape}}
' - + 'P Value: {{{{namespace[assoc]}}log_pvalue|logtoscinotation|htmlescape}}
' + - '{{#if {{namespace[credset]}}posterior_prob}}
Posterior probability: {{{{namespace[credset]}}posterior_prob|scinotation|htmlescape}}{{/if}}', + html: '{{assoc.variant|htmlescape}}
' + + 'P Value: {{assoc.log_pvalue|logtoscinotation|htmlescape}}
' + + '{{#if credset.posterior_prob}}
Posterior probability: {{credset.posterior_prob|scinotation|htmlescape}}{{/if}}', }; LocusZoom.Layouts.add('tooltip', 'annotation_credible_set', annotation_credible_set_tooltip); @@ -188,14 +188,14 @@ function install (LocusZoom) { fill_opacity: 0.7, tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set', { unnamespaced: true }), fields: [ - '{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', - '{{namespace[assoc]}}log_pvalue', '{{namespace[assoc]}}log_pvalue|logtoscinotation', - '{{namespace[assoc]}}ref_allele', - '{{namespace[credset]}}posterior_prob', '{{namespace[credset]}}contrib_fraction', - '{{namespace[credset]}}is_member', - '{{namespace[ld]}}state', '{{namespace[ld]}}isrefvar', + 'assoc.variant', 'assoc.position', + 'assoc.log_pvalue', 'assoc.log_pvalue|logtoscinotation', + 'assoc.ref_allele', + 'credset.posterior_prob', 'credset.contrib_fraction', + 'credset.is_member', + 'ld.state', 'ld.isrefvar', ], - match: { send: '{{namespace[assoc]}}variant', receive: '{{namespace[assoc]}}variant' }, + match: { send: 'assoc.variant', receive: 'assoc.variant' }, }); base.color.unshift({ field: 'lz_is_match', // Special field name whose presence triggers custom rendering @@ -219,9 +219,9 @@ function install (LocusZoom) { namespace: { 'assoc': 'assoc', 'credset': 'credset' }, id: 'annotationcredibleset', type: 'annotation_track', - id_field: '{{namespace[assoc]}}variant', + id_field: 'assoc.variant', x_axis: { - field: '{{namespace[assoc]}}position', + field: 'assoc.position', }, color: [ { @@ -234,11 +234,11 @@ function install (LocusZoom) { }, '#00CC00', ], - fields: ['{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', '{{namespace[assoc]}}log_pvalue', '{{namespace[credset]}}posterior_prob', '{{namespace[credset]}}contrib_fraction', '{{namespace[credset]}}is_member'], - match: { send: '{{namespace[assoc]}}variant', receive: '{{namespace[assoc]}}variant' }, + fields: ['assoc.variant', 'assoc.position', 'assoc.log_pvalue', 'credset.posterior_prob', 'credset.contrib_fraction', 'credset.is_member'], + match: { send: 'assoc.variant', receive: 'assoc.variant' }, filters: [ // Specify which points to show on the track. Any selection must satisfy ALL filters - { field: '{{namespace[credset]}}is_member', operator: '=', value: true }, + { field: 'credset.is_member', operator: '=', value: true }, ], behaviors: { onmouseover: [ @@ -324,7 +324,7 @@ function install (LocusZoom) { point_shape: 'circle', point_size: 40, color: { - field: '{{namespace[credset]}}is_member', + field: 'credset.is_member', scale_function: 'if', parameters: { field_value: true, @@ -358,7 +358,7 @@ function install (LocusZoom) { point_size: 40, color: [ { - field: '{{namespace[credset]}}contrib_fraction', + field: 'credset.contrib_fraction', scale_function: 'if', parameters: { field_value: 0, @@ -367,7 +367,7 @@ function install (LocusZoom) { }, { scale_function: 'interpolate', - field: '{{namespace[credset]}}contrib_fraction', + field: 'credset.contrib_fraction', parameters: { breaks: [0, 1], values: ['#fafe87', '#9c0000'], diff --git a/esm/ext/lz-intervals-enrichment.js b/esm/ext/lz-intervals-enrichment.js index a3abe606..055fe165 100644 --- a/esm/ext/lz-intervals-enrichment.js +++ b/esm/ext/lz-intervals-enrichment.js @@ -157,10 +157,10 @@ function install(LocusZoom) { closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: `Tissue: {{{{namespace[intervals]}}tissueId|htmlescape}}
- Range: {{{{namespace[intervals]}}chromosome|htmlescape}}: {{{{namespace[intervals]}}start|htmlescape}}-{{{{namespace[intervals]}}end|htmlescape}}
- -log10 p: {{{{namespace[intervals]}}pValue|neglog10|scinotation|htmlescape}}
- Enrichment (n-fold): {{{{namespace[intervals]}}fold|scinotation|htmlescape}}`, + html: `Tissue: {{intervals.tissueId|htmlescape}}
+ Range: {{intervals.chromosome|htmlescape}}: {{intervals.start|htmlescape}}-{{intervals.end|htmlescape}}
+ -log10 p: {{intervals.pValue|neglog10|scinotation|htmlescape}}
+ Enrichment (n-fold): {{intervals.fold|scinotation|htmlescape}}`, }; /** @@ -176,19 +176,19 @@ function install(LocusZoom) { id: 'intervals_enrichment', type: 'intervals_enrichment', tag: 'intervals_enrichment', - match: { send: '{{namespace[intervals]}}tissueId' }, - fields: ['{{namespace[intervals]}}chromosome', '{{namespace[intervals]}}start', '{{namespace[intervals]}}end', '{{namespace[intervals]}}pValue', '{{namespace[intervals]}}fold', '{{namespace[intervals]}}tissueId', '{{namespace[intervals]}}ancestry'], - id_field: '{{namespace[intervals]}}start', // not a good ID field for overlapping intervals - start_field: '{{namespace[intervals]}}start', - end_field: '{{namespace[intervals]}}end', + match: { send: 'intervals.tissueId' }, + fields: ['intervals.chromosome', 'intervals.start', 'intervals.end', 'intervals.pValue', 'intervals.fold', 'intervals.tissueId', 'intervals.ancestry'], + id_field: 'intervals.start', // not a good ID field for overlapping intervals + start_field: 'intervals.start', + end_field: 'intervals.end', filters: [ - { field: '{{namespace[intervals]}}ancestry', operator: '=', value: 'EU' }, - { field: '{{namespace[intervals]}}pValue', operator: '<=', value: 0.05 }, - { field: '{{namespace[intervals]}}fold', operator: '>', value: 2.0 }, + { field: 'intervals.ancestry', operator: '=', value: 'EU' }, + { field: 'intervals.pValue', operator: '<=', value: 0.05 }, + { field: 'intervals.fold', operator: '>', value: 2.0 }, ], y_axis: { axis: 1, - field: '{{namespace[intervals]}}fold', // is this used for other than extent generation? + field: 'intervals.fold', // is this used for other than extent generation? floor: 0, upper_buffer: 0.10, min_extent: [0, 10], @@ -196,7 +196,7 @@ function install(LocusZoom) { fill_opacity: 0.5, // Many intervals overlap: show all, even if the ones below can't be clicked color: [ { - field: '{{namespace[intervals]}}tissueId', + field: 'intervals.tissueId', scale_function: 'stable_choice', parameters: { values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], @@ -227,19 +227,19 @@ function install(LocusZoom) { id: 'interval_matches', type: 'highlight_regions', namespace: { intervals: 'intervals' }, - match: { receive: '{{namespace[intervals]}}tissueId' }, - fields: ['{{namespace[intervals]}}start', '{{namespace[intervals]}}end', '{{namespace[intervals]}}tissueId', '{{namespace[intervals]}}ancestry', '{{namespace[intervals]}}pValue', '{{namespace[intervals]}}fold'], - start_field: '{{namespace[intervals]}}start', - end_field: '{{namespace[intervals]}}end', - merge_field: '{{namespace[intervals]}}tissueId', + match: { receive: 'intervals.tissueId' }, + fields: ['intervals.start', 'intervals.end', 'intervals.tissueId', 'intervals.ancestry', 'intervals.pValue', 'intervals.fold'], + start_field: 'intervals.start', + end_field: 'intervals.end', + merge_field: 'intervals.tissueId', filters: [ { field: 'lz_is_match', operator: '=', value: true }, - { field: '{{namespace[intervals]}}ancestry', operator: '=', value: 'EU' }, - { field: '{{namespace[intervals]}}pValue', operator: '<=', value: 0.05 }, - { field: '{{namespace[intervals]}}fold', operator: '>', value: 2.0 }, + { field: 'intervals.ancestry', operator: '=', value: 'EU' }, + { field: 'intervals.pValue', operator: '<=', value: 0.05 }, + { field: 'intervals.fold', operator: '>', value: 2.0 }, ], color: [{ - field: '{{namespace[intervals]}}tissueId', + field: 'intervals.tissueId', scale_function: 'stable_choice', parameters: { values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index cfa59847..115fac15 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -605,7 +605,7 @@ function install (LocusZoom) { closable: false, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: '{{{{namespace[intervals]}}state_name|htmlescape}}
{{{{namespace[intervals]}}start|htmlescape}}-{{{{namespace[intervals]}}end|htmlescape}}', + html: '{{intervals.state_name|htmlescape}}
{{intervals.start|htmlescape}}-{{intervals.end|htmlescape}}', }; /** @@ -620,23 +620,23 @@ function install (LocusZoom) { id: 'intervals', type: 'intervals', tag: 'intervals', - fields: ['{{namespace[intervals]}}start', '{{namespace[intervals]}}end', '{{namespace[intervals]}}state_id', '{{namespace[intervals]}}state_name', '{{namespace[intervals]}}itemRgb'], - id_field: '{{namespace[intervals]}}start', // FIXME: This is not a good D3 "are these datums redundant" ID for datasets with multiple intervals heavily overlapping - start_field: '{{namespace[intervals]}}start', - end_field: '{{namespace[intervals]}}end', - track_split_field: '{{namespace[intervals]}}state_name', - track_label_field: '{{namespace[intervals]}}state_name', + fields: ['intervals.start', 'intervals.end', 'intervals.state_id', 'intervals.state_name', 'intervals.itemRgb'], + id_field: 'intervals.start', // FIXME: This is not a good D3 "are these datums redundant" ID for datasets with multiple intervals heavily overlapping + start_field: 'intervals.start', + end_field: 'intervals.end', + track_split_field: 'intervals.state_name', + track_label_field: 'intervals.state_name', split_tracks: false, always_hide_legend: true, color: [ { // If present, an explicit color field will override any other option (and be used to auto-generate legend) - field: '{{namespace[intervals]}}itemRgb', + field: 'intervals.itemRgb', scale_function: 'to_rgb', }, { // TODO: Consider changing this to stable_choice in the future, for more stable coloring - field: '{{namespace[intervals]}}state_name', + field: 'intervals.state_name', scale_function: 'categorical_bin', parameters: { // Placeholder. Empty categories and values will automatically be filled in when new data loads. diff --git a/esm/layouts/index.js b/esm/layouts/index.js index aedf82ce..20cf9dca 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -27,10 +27,10 @@ const standard_association_tooltip = { closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: `{{{{namespace[assoc]}}variant|htmlescape}}
- P Value: {{{{namespace[assoc]}}log_pvalue|logtoscinotation|htmlescape}}
- Ref. Allele: {{{{namespace[assoc]}}ref_allele|htmlescape}}
- {{#if {{namespace[ld]}}isrefvar}}LD Reference Variant{{#else}} + html: `{{assoc.variant|htmlescape}}
+ P Value: {{assoc.log_pvalue|logtoscinotation|htmlescape}}
+ Ref. Allele: {{assoc.ref_allele|htmlescape}}
+ {{#if ld.isrefvar}}LD Reference Variant{{#else}} {{{{namespace[catalog]}}variant|htmlescape}}
' + html: '{{catalog.variant|htmlescape}}
' + 'Catalog entries: {{n_catalog_matches|htmlescape}}
' - + 'Top Trait: {{{{namespace[catalog]}}trait|htmlescape}}
' - + 'Top P Value: {{{{namespace[catalog]}}log_pvalue|logtoscinotation}}
' + + 'Top Trait: {{catalog.trait|htmlescape}}
' + + 'Top P Value: {{catalog.log_pvalue|logtoscinotation}}
' // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL - + 'More:
GWAS catalog / dbSNP', + + 'More: GWAS catalog / dbSNP', }; const coaccessibility_tooltip = { @@ -85,11 +85,11 @@ const coaccessibility_tooltip = { hide: { and: ['unhighlighted', 'unselected'] }, // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element) html: 'Regulatory element
' + - '{{{{namespace[access]}}start1|htmlescape}}-{{{{namespace[access]}}end1|htmlescape}}
' + + '{{access.start1|htmlescape}}-{{access.end1|htmlescape}}
' + 'Promoter
' + - '{{{{namespace[access]}}start2|htmlescape}}-{{{{namespace[access]}}end2|htmlescape}}
' + - '{{#if {{namespace[access]}}target}}Target: {{{{namespace[access]}}target|htmlescape}}
{{/if}}' + - 'Score: {{{{namespace[access]}}score|htmlescape}}', + '{{access.start2|htmlescape}}-{{access.end2|htmlescape}}
' + + '{{#if access.target}}Target: {{access.target|htmlescape}}
{{/if}}' + + 'Score: {{access.score|htmlescape}}', }; /* @@ -119,18 +119,18 @@ const recomb_rate_layer = { id: 'recombrate', type: 'line', tag: 'recombination', - fields: ['{{namespace[recomb]}}position', '{{namespace[recomb]}}recomb_rate'], + fields: ['recomb.position', 'recomb.recomb_rate'], z_index: 1, style: { 'stroke': '#0000FF', 'stroke-width': '1.5px', }, x_axis: { - field: '{{namespace[recomb]}}position', + field: 'recomb.position', }, y_axis: { axis: 2, - field: '{{namespace[recomb]}}recomb_rate', + field: 'recomb.recomb_rate', floor: 0, ceiling: 100, }, @@ -142,18 +142,25 @@ const recomb_rate_layer = { * @type data_layer */ const association_pvalues_layer = { - namespace: { 'assoc': 'assoc', 'ld': 'ld' }, + // FIXME: implement fields contract + namespace: { 'assoc': 'assoc' }, // 'ld(assoc)': 'ld' }, + // join_options: [{ + // type: 'left_match', + // name: 'combined', + // requires: ['assoc', 'ld'], + // params: ['assoc.variant', 'ld.variant'], + // }], id: 'associationpvalues', type: 'scatter', tag: 'association', - fields: ['{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', '{{namespace[assoc]}}log_pvalue', '{{namespace[assoc]}}log_pvalue|logtoscinotation', '{{namespace[assoc]}}ref_allele', '{{namespace[ld]}}state', '{{namespace[ld]}}isrefvar'], - id_field: '{{namespace[assoc]}}variant', + fields: ['assoc.variant', 'assoc.position', 'assoc.log_pvalue', 'assoc.log_pvalue|logtoscinotation', 'assoc.ref_allele', 'ld.state', 'ld.isrefvar'], + id_field: 'assoc.variant', coalesce: { active: true, }, point_shape: { scale_function: 'if', - field: '{{namespace[ld]}}isrefvar', + field: 'ld.isrefvar', parameters: { field_value: 1, then: 'diamond', @@ -162,7 +169,7 @@ const association_pvalues_layer = { }, point_size: { scale_function: 'if', - field: '{{namespace[ld]}}isrefvar', + field: 'ld.isrefvar', parameters: { field_value: 1, then: 80, @@ -172,7 +179,7 @@ const association_pvalues_layer = { color: [ { scale_function: 'if', - field: '{{namespace[ld]}}isrefvar', + field: 'ld.isrefvar', parameters: { field_value: 1, then: '#9632b8', @@ -180,7 +187,7 @@ const association_pvalues_layer = { }, { scale_function: 'numerical_bin', - field: '{{namespace[ld]}}state', + field: 'ld.state', parameters: { breaks: [0, 0.2, 0.4, 0.6, 0.8], // Derived from Google "Turbo" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85] @@ -201,11 +208,11 @@ const association_pvalues_layer = { label: null, z_index: 2, x_axis: { - field: '{{namespace[assoc]}}position', + field: 'assoc.position', }, y_axis: { axis: 1, - field: '{{namespace[assoc]}}log_pvalue', + field: 'assoc.log_pvalue', floor: 0, upper_buffer: 0.10, min_extent: [0, 10], @@ -234,11 +241,11 @@ const coaccessibility_layer = { id: 'coaccessibility', type: 'arcs', tag: 'coaccessibility', - fields: ['{{namespace[access]}}start1', '{{namespace[access]}}end1', '{{namespace[access]}}start2', '{{namespace[access]}}end2', '{{namespace[access]}}id', '{{namespace[access]}}target', '{{namespace[access]}}score'], - match: { send: '{{namespace[access]}}target', receive: '{{namespace[access]}}target' }, - id_field: '{{namespace[access]}}id', + fields: ['access.start1', 'access.end1', 'access.start2', 'access.end2', 'access.id', 'access.target', 'access.score'], + match: { send: 'access.target', receive: 'access.target' }, + id_field: 'access.id', filters: [ - { field: '{{namespace[access]}}score', operator: '!=', value: null }, + { field: 'access.score', operator: '!=', value: null }, ], color: [ { @@ -265,12 +272,12 @@ const coaccessibility_layer = { }, ], x_axis: { - field1: '{{namespace[access]}}start1', - field2: '{{namespace[access]}}start2', + field1: 'access.start1', + field2: 'access.start2', }, y_axis: { axis: 1, - field: '{{namespace[access]}}score', + field: 'access.score', upper_buffer: 0.1, min_extent: [0, 1], }, @@ -297,9 +304,9 @@ const association_pvalues_catalog_layer = function () { // Slightly modify an existing layout let base = deepCopy(association_pvalues_layer); base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base); - base.tooltip.html += '{{#if {{namespace[catalog]}}rsid}}
See hits in GWAS catalog{{/if}}'; + base.tooltip.html += '{{#if catalog.rsid}}
See hits in GWAS catalog{{/if}}'; base.namespace.catalog = 'catalog'; - base.fields.push('{{namespace[catalog]}}rsid', '{{namespace[catalog]}}trait', '{{namespace[catalog]}}log_pvalue'); + base.fields.push('catalog.rsid', 'catalog.trait', 'catalog.log_pvalue'); return base; }(); @@ -316,22 +323,22 @@ const phewas_pvalues_layer = { point_shape: 'circle', point_size: 70, tooltip_positioning: 'vertical', - id_field: '{{namespace[phewas]}}id', - fields: ['{{namespace[phewas]}}id', '{{namespace[phewas]}}log_pvalue', '{{namespace[phewas]}}trait_group', '{{namespace[phewas]}}trait_label'], + id_field: 'phewas.id', + fields: ['phewas.id', 'phewas.log_pvalue', 'phewas.trait_group', 'phewas.trait_label'], x_axis: { - field: '{{namespace[phewas]}}x', // Synthetic/derived field added by `category_scatter` layer - category_field: '{{namespace[phewas]}}trait_group', + field: 'phewas.x', // Synthetic/derived field added by `category_scatter` layer + category_field: 'phewas.trait_group', lower_buffer: 0.025, upper_buffer: 0.025, }, y_axis: { axis: 1, - field: '{{namespace[phewas]}}log_pvalue', + field: 'phewas.log_pvalue', floor: 0, upper_buffer: 0.15, }, color: [{ - field: '{{namespace[phewas]}}trait_group', + field: 'phewas.trait_group', scale_function: 'categorical_bin', parameters: { categories: [], @@ -345,9 +352,9 @@ const phewas_pvalues_layer = { show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, html: [ - 'Trait: {{{{namespace[phewas]}}trait_label|htmlescape}}
', - 'Trait Category: {{{{namespace[phewas]}}trait_group|htmlescape}}
', - 'P-value: {{{{namespace[phewas]}}log_pvalue|logtoscinotation|htmlescape}}
', + 'Trait: {{phewas.trait_label|htmlescape}}
', + 'Trait Category: {{phewas.trait_group|htmlescape}}
', + 'P-value: {{phewas.log_pvalue|logtoscinotation|htmlescape}}
', ].join(''), }, behaviors: { @@ -362,7 +369,7 @@ const phewas_pvalues_layer = { ], }, label: { - text: '{{{{namespace[phewas]}}trait_label}}', + text: '{{phewas.trait_label}}', spacing: 6, lines: { style: { @@ -373,7 +380,7 @@ const phewas_pvalues_layer = { }, filters: [ { - field: '{{namespace[phewas]}}log_pvalue', + field: 'phewas.log_pvalue', operator: '>=', value: 20, }, @@ -395,7 +402,7 @@ const genes_layer = { id: 'genes', type: 'genes', tag: 'genes', - fields: ['{{namespace[gene]}}all', '{{namespace[constraint]}}all'], + fields: ['gene.all', 'constraint.all'], id_field: 'gene_id', behaviors: { onmouseover: [ @@ -449,20 +456,20 @@ const annotation_catalog_layer = { id: 'annotation_catalog', type: 'annotation_track', tag: 'gwascatalog', - id_field: '{{namespace[assoc]}}variant', + id_field: 'assoc.variant', x_axis: { - field: '{{namespace[assoc]}}position', + field: 'assoc.position', }, color: '#0000CC', fields: [ - '{{namespace[assoc]}}variant', '{{namespace[assoc]}}chromosome', '{{namespace[assoc]}}position', - '{{namespace[catalog]}}variant', '{{namespace[catalog]}}rsid', '{{namespace[catalog]}}trait', - '{{namespace[catalog]}}log_pvalue', '{{namespace[catalog]}}pos', + 'assoc.variant', 'assoc.chromosome', 'assoc.position', + 'catalog.variant', 'catalog.rsid', 'catalog.trait', + 'catalog.log_pvalue', 'catalog.pos', ], filters: [ // Specify which points to show on the track. Any selection must satisfy ALL filters - { field: '{{namespace[catalog]}}rsid', operator: '!=', value: null }, - { field: '{{namespace[catalog]}}log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, + { field: 'catalog.rsid', operator: '!=', value: null }, + { field: 'catalog.log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, ], behaviors: { onmouseover: [ @@ -717,8 +724,8 @@ const association_panel = { x_linked: true, }, data_layers: [ - deepCopy(significance_layer), - deepCopy(recomb_rate_layer), + // deepCopy(significance_layer), + // deepCopy(recomb_rate_layer), deepCopy(association_pvalues_layer), ], }; @@ -788,7 +795,7 @@ const association_catalog_panel = function () { display_name: 'Label catalog traits', // Human readable representation of field name display: { // Specify layout directives that control display of the plot for this option label: { - text: '{{{{namespace[catalog]}}trait}}', + text: '{{catalog.trait}}', spacing: 6, lines: { style: { @@ -800,9 +807,9 @@ const association_catalog_panel = function () { filters: [ // Only label points if they are significant for some trait in the catalog, AND in high LD // with the top hit of interest - { field: '{{namespace[catalog]}}trait', operator: '!=', value: null }, - { field: '{{namespace[catalog]}}log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, - { field: '{{namespace[ld]}}state', operator: '>', value: 0.4 }, + { field: 'catalog.trait', operator: '!=', value: null }, + { field: 'catalog.log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, + { field: 'ld.state', operator: '>', value: 0.4 }, ], style: { 'font-size': '10px', @@ -933,7 +940,7 @@ const standard_association_plot = { toolbar: standard_association_toolbar, panels: [ deepCopy(association_panel), - deepCopy(genes_panel), + // deepCopy(genes_panel), ], }; diff --git a/index.html b/index.html index adbe0373..71529cc9 100644 --- a/index.html +++ b/index.html @@ -202,15 +202,15 @@
Multiple Phenotypes (Layered)
if (online) { apiBase = "https://portaldev.sph.umich.edu/api/v1/"; data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", params: { source: 45, id_field: "variant" }}]) - .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", params: { source: '1000G', build: 'GRCh37', population: 'ALL' } }]) - .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", params: { build: 'GRCh37' } }]) - .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", params: { build: 'GRCh37' } }]) - .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("assoc", ["AssociationLZ", {base_url: apiBase + "statistic/single/", source: 45 }]); + // .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", params: { source: '1000G', build: 'GRCh37', population: 'ALL' } }]) + // .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", params: { build: 'GRCh37' } }]) + // .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", params: { build: 'GRCh37' } }]) + // .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); } else { apiBase = window.location.origin + window.location.pathname.substr(0, window.location.pathname.lastIndexOf("/") + 1) + "examples/data/"; data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", {url: apiBase + "assoc_10_114550452-115067678.json?", params: { source: 45, id_field: "variant" }}]) + .add("assoc", ["AssociationLZ", {base_url: apiBase + "assoc_10_114550452-115067678.json?", source: 45 }]) .add("ld", ["LDServer", { url: apiBase + "ld_10_114550452-115067678.json?" , params: { build: 'GRCh37' }}]) .add("gene", ["GeneLZ", { url: apiBase + "genes_10_114550452-115067678.json?", params: { build: 'GRCh37' } }]) .add("recomb", ["RecombLZ", { url: apiBase + "recomb_10_114550452-115067678.json?", params: { build: 'GRCh37' } }]) From b61b06e7e11131b4c8ae6b81c1497303765593de Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 2 Sep 2021 00:32:09 -0400 Subject: [PATCH 003/100] Add unit tests around requester join ops --- esm/data/requester.js | 2 +- test/unit/data/test_requester.js | 33 +++++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/esm/data/requester.js b/esm/data/requester.js index 2c546087..47044166 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -6,7 +6,7 @@ import { JOINS } from '../registry'; class JoinTask { constructor(join_type, params) { this._callable = JOINS.get(join_type); - this._params = params; + this._params = params || []; } getData(options, left, right) { diff --git a/test/unit/data/test_requester.js b/test/unit/data/test_requester.js index f6d32a5a..ee91bf7c 100644 --- a/test/unit/data/test_requester.js +++ b/test/unit/data/test_requester.js @@ -1,15 +1,24 @@ import {assert} from 'chai'; import Requester, {_JoinTask} from '../../../esm/data/requester'; +import {JOINS} from '../../../esm/registry'; describe('Requester object defines and parses requests', function () { describe('Layout parsing', function () { + before(function () { + JOINS.add('sumtwo', (left, right, some_param) => left + right + some_param); + }); + + after(function () { + JOINS.remove('sumtwo'); + }); + beforeEach(function () { this._all_datasources = new Map([ - ['assoc1', {name: 'assoc1'}], - ['someld', {name: 'someld'}], - ['assoc2', {name: 'assoc2'}], + ['assoc1', { name: 'assoc1', getData: () => Promise.resolve(1) }], + ['someld', { name: 'someld', getData: () => Promise.resolve(2) }], + ['assoc2', { name: 'assoc2' }], ]); this._requester = new Requester(this._all_datasources); }); @@ -31,8 +40,8 @@ describe('Requester object defines and parses requests', function () { // Validate that correct dependencies were wired up assert.deepEqual([...entities.keys()], ['assoc', 'ld', 'combined']); - assert.deepEqual(entities.get('assoc'), {name: 'assoc1'}); - assert.deepEqual(entities.get('ld'), {name: 'someld'}); + assert.deepEqual(entities.get('assoc').name, 'assoc1'); + assert.deepEqual(entities.get('ld').name, 'someld'); assert.instanceOf(entities.get('combined'), _JoinTask, 'A join task was created'); }); @@ -56,5 +65,19 @@ describe('Requester object defines and parses requests', function () { ); }, /join name 'combined' must be unique/); }); + + it('performs joins based on layout spec', function () { + const namespace_options = {'assoc': 'assoc1', 'ld(assoc)': 'someld'}; + const join_options = [{ + type: 'sumtwo', + name: 'combined', + requires: ['assoc', 'ld'], + params: [3], // tests that params get passed, and can be whatever a join function needs + }]; + return this._requester.getData({}, namespace_options, join_options) + .then((res) => { + assert.equal(res, 6); + }); + }); }); }); From 7e10b737131477ed06f4f7d4ed39a4ca1b971be5 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Fri, 3 Sep 2021 19:56:13 -0400 Subject: [PATCH 004/100] Convert adapters and demos to new separate join syntax, and update demos where appropriate --- esm/data/adapters.js | 816 +++++------------- esm/data/requester.js | 10 +- esm/ext/lz-credible-sets.js | 81 +- esm/ext/lz-intervals-track.js | 12 +- esm/ext/lz-tabix-source.js | 39 +- esm/layouts/index.js | 54 +- esm/registry/joins.js | 49 ++ examples/coaccessibility.html | 12 +- examples/credible_sets.html | 13 +- examples/gwas_catalog.html | 6 +- examples/interval_annotations.html | 12 +- examples/interval_enrichment.html | 10 +- examples/js/aggregation-tests-example-page.js | 10 +- examples/misc/covariates_model.html | 10 +- examples/multiple_phenotypes_layered.html | 11 +- examples/phewas_forest.html | 28 +- examples/phewas_scatter.html | 12 +- index.html | 14 +- 18 files changed, 431 insertions(+), 768 deletions(-) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 0a2dbd25..07f44122 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -35,9 +35,8 @@ import {BaseUrlAdapter} from 'undercomplicate'; -function validateBuildSource() { - // TODO: deleteme -} +const REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/; + class BaseLZAdapter extends BaseUrlAdapter { constructor(config) { @@ -77,6 +76,26 @@ class BaseLZAdapter extends BaseUrlAdapter { ); }); } + + /** + * Convenience method, manually called in LZ sources that deal with dependent data. + * + * In the last step of fetching data, LZ adds a prefix to each field name. + * This means that operations like "build query based on prior data" can't just ask for "log_pvalue" because + * they are receiving "assoc.log_pvalue" or some such unknown prefix. + * + * This lets use easily use dependent data + * + * @private + */ + _findPrefixedKey(a_record, fieldname) { + const suffixer = new RegExp(`\\.${fieldname}$`); + const match = Object.keys(a_record).find((key) => suffixer.test(key)); + if (!match) { + throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`); + } + return match; + } } @@ -152,16 +171,137 @@ class AssociationLZ extends BaseUMAdapter { _getURL (state) { const {chr, start, end} = state; - return `${this._base_url}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`; + return `${this._url}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`; } } -// class LDServer2 extends BaseUMAdapter { +class LDServer extends BaseUMAdapter { + constructor(config) { + // item1 = refvar, item2 = othervar + config.fields = ['chromosome2', 'position2', 'variant2', 'correlation']; + super(config); + } + + _getURL(request_options) { + // The LD source/pop can be overridden from plot.state for dynamic layouts + const build = request_options.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted. + let source = request_options.ld_source || this._config.source || '1000G'; + const population = request_options.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL + const method = this._config.method || 'rsquare'; + + if (source === '1000G' && build === 'GRCh38') { + // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default. + source = '1000G-FRZ09'; + } + + this._validateBuildSource(build, null); // LD doesn't need to validate `source` option + + const refvar = request_options.ld_refvar; + + return [ + this._url, 'genome_builds/', build, '/references/', source, '/populations/', population, '/variants', + '?correlation=', method, + '&variant=', encodeURIComponent(refvar), + '&chrom=', encodeURIComponent(request_options.chr), + '&start=', encodeURIComponent(request_options.start), + '&stop=', encodeURIComponent(request_options.end), + ].join(''); + } + + _buildRequestOptions(state, assoc_data) { + // If no state refvar is provided, find the most significant variant in any provided assoc data. Assumes that assoc satisfies the "assoc" fields contract, eg has fields variant and log_pvalue + const base = super._buildRequestOptions(...arguments); + if (!assoc_data.length) { + base._skip_request = true; + return base; + } + + const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant'); + const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue'); + + // Determine the reference variant (via user selected OR automatic-per-track) + let refvar; + let best_hit = {}; + if (state.ldrefvar) { + // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data + // FIXME: mark Ld refvar for top hits. What if assoc format is different than how we talk to the server? + refvar = state.ldrefvar; + best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {}; + } else { + // find highest log-value and associated var spec + let best_logp = 0; + for (let item of assoc_data) { + const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item; + if (log_pvalue > best_logp) { + best_logp = log_pvalue; + refvar = variant; + best_hit = item; + } + } + } + + // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting. + // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase. + best_hit.lz_is_ld_refvar = true; + + // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server, + // the variant fields must be normalized to a specific format. All later LD operations will use that format. + const match = refvar && refvar.match(REGEX_MARKER); + if (!match) { + throw new Error('Could not request LD for a missing or incomplete marker format'); + } + + const [_, chrom, pos, ref, alt] = match; + // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing + // a partial match at most leaves room for potential future features. + refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip? + if (ref && alt) { + refvar += `_${ref}/${alt}`; + } + + base.ld_refvar = refvar; + return base; + } -// } + _getCacheKey(options) { + // LD is keyed by more than just region; use full URL as cache key + // TODO: Support multiregion cache calls + return this._getURL(options); + } + + _performRequest(options) { + // Skip request if this one depends on other data, in a region with no data + if (options._skip_request) { + return Promise.resolve([]); + } + // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected + const url = this._getURL(options); + + let combined = { data: {} }; + let chainRequests = function (url) { + return fetch(url).then().then((response) => { + if (!response.ok) { + throw new Error(response.statusText); + } + return response.text(); + }).then(function(payload) { + payload = JSON.parse(payload); + Object.keys(payload.data).forEach(function (key) { + combined.data[key] = (combined.data[key] || []).concat(payload.data[key]); + }); + if (payload.next) { + return chainRequests(payload.next); + } + return combined; + }); + }; + return chainRequests(url) + .then((response) => JSON.stringify(response)); + } +} // NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs. @@ -539,317 +679,6 @@ class BaseApiAdapter extends BaseAdapter { } -/** - * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant. - * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS - * variant (smallest pvalue or largest neg_log_pvalue) and yse that as the LD reference variant. - * - * This source is designed to connect its results to association data, and therefore depends on association data having - * been loaded by a previous request in the data chain. For custom association APIs, some additional options might - * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt` - * are preferred, but this source will attempt to harmonize other common data formats into something that the LD - * server can understand. - * - * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter - */ -class LDServer extends BaseApiAdapter { - /** - * @param {string} config.url The base URL for the remote data. - * @param {object} config.params - * @param [config.params.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant. - * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. - * @param [config.params.source='1000G'] The name of the reference panel to use, as specified in the LD server instance. - * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display. - * @param [config.params.population='ALL'] The sample population used to calculate LD for a specified source; - * population names vary depending on the reference panel and how the server was populated wth data. - * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display. - * @param [config.params.method='rsquare'] The metric used to calculate LD - * @param [config.params.id_field] The association data field that contains variant identifier information. The preferred - * format of LD server is `chrom:pos_ref/alt` and this source will attempt to normalize other common formats. - * This source can auto-detect possible matches for field names containing "variant" or "id" - * @param [config.params.position_field] The association data field that contains variant position information. - * This source can auto-detect possible matches for field names containing "position" or "pos" - * @param [config.params.pvalue_field] The association data field that contains pvalue information. - * This source can auto-detect possible matches for field names containing "pvalue" or "log_pvalue". - * The suggested LD refvar will be the smallest pvalue, or the largest log_pvalue: this source will auto-detect - * the word "log" in order to determine the sign of the comparison. - */ - constructor(config) { - super(config); - this.__dependentSource = true; - } - - preGetData(state, fields) { - if (fields.length > 1) { - if (fields.length !== 2 || !fields.includes('isrefvar')) { - throw new Error(`LD does not know how to get all fields: ${fields.join(', ')}`); - } - } - } - - findMergeFields(chain) { - // Find the fields (as provided by a previous step in the chain, like an association source) that will be needed to - // combine LD data with existing information - - // Since LD information may be shared across multiple assoc sources with different namespaces, - // we use regex to find columns to join on, rather than requiring exact matches - const exactMatch = function (arr) { - return function () { - const regexes = arguments; - for (let i = 0; i < regexes.length; i++) { - const regex = regexes[i]; - const m = arr.filter(function (x) { - return x.match(regex); - }); - if (m.length) { - return m[0]; - } - } - return null; - }; - }; - let dataFields = { - id: this.params.id_field, - position: this.params.position_field, - pvalue: this.params.pvalue_field, - _names_:null, - }; - if (chain && chain.body && chain.body.length > 0) { - const names = Object.keys(chain.body[0]); - const nameMatch = exactMatch(names); - // Internally, fields are generally prefixed with the name of the source they come from. - // If the user provides an id_field (like `variant`), it should work across data sources (`assoc1:variant`, - // assoc2:variant), but not match fragments of other field names (assoc1:variant_thing) - // Note: these lookups hard-code a couple of common fields that will work based on known APIs in the wild - const id_match = dataFields.id && nameMatch(new RegExp(`${dataFields.id}\\b`)); - dataFields.id = id_match || nameMatch(/\bvariant\b/) || nameMatch(/\bid\b/); - dataFields.position = dataFields.position || nameMatch(/\bposition\b/i, /\bpos\b/i); - dataFields.pvalue = dataFields.pvalue || nameMatch(/\bpvalue\b/i, /\blog_pvalue\b/i); - dataFields._names_ = names; - } - return dataFields; - } - - findRequestedFields (fields, outnames) { - // Assumption: all usages of this source will only ever ask for "isrefvar" or "state". This maps to output names. - let obj = {}; - for (let i = 0; i < fields.length; i++) { - if (fields[i] === 'isrefvar') { - obj.isrefvarin = fields[i]; - obj.isrefvarout = outnames && outnames[i]; - } else { - obj.ldin = fields[i]; - obj.ldout = outnames && outnames[i]; - } - } - return obj; - } - - /** - * The LD API payload does not obey standard format conventions; do not try to transform it. - */ - normalizeResponse (data) { - return data; - } - - /** - * Get the LD reference variant, which by default will be the most significant hit in the assoc results - * This will be used in making the original query to the LD server for pairwise LD information. - * - * This is meant to join a single LD request to any number of association results, and to work with many kinds of API. - * To do this, the datasource looks for fields with special known names such as pvalue, log_pvalue, etc. - * If your API uses different nomenclature, an option must be specified. - * - * @protected - * @returns {String[]} Two strings: 1) the marker id (expected to be in `chr:pos_ref/alt` format) of the reference - * variant, and 2) the marker ID as it appears in the original dataset that we are joining to, so that the exact - * refvar can be marked when plotting the data.. - */ - getRefvar(state, chain, fields) { - let findExtremeValue = function(records, pval_field) { - // Finds the most significant hit (smallest pvalue, or largest -log10p). Will try to auto-detect the appropriate comparison. - pval_field = pval_field || 'log_pvalue'; // The official LZ API returns log_pvalue - const is_log = /log/.test(pval_field); - let cmp; - if (is_log) { - cmp = function(a, b) { - return a > b; - }; - } else { - cmp = function(a, b) { - return a < b; - }; - } - let extremeVal = records[0][pval_field], extremeIdx = 0; - for (let i = 1; i < records.length; i++) { - if (cmp(records[i][pval_field], extremeVal)) { - extremeVal = records[i][pval_field]; - extremeIdx = i; - } - } - return extremeIdx; - }; - - let reqFields = this.findRequestedFields(fields); - let refVar = reqFields.ldin; - if (refVar === 'state') { - refVar = state.ldrefvar || chain.header.ldrefvar || 'best'; - } - if (refVar === 'best') { - if (!chain.body) { - throw new Error('No association data found to find best pvalue'); - } - let keys = this.findMergeFields(chain); - if (!keys.pvalue || !keys.id) { - let columns = ''; - if (!keys.id) { - columns += `${columns.length ? ', ' : ''}id`; - } - if (!keys.pvalue) { - columns += `${columns.length ? ', ' : ''}pvalue`; - } - throw new Error(`Unable to find necessary column(s) for merge: ${columns} (available: ${keys._names_})`); - } - refVar = chain.body[findExtremeValue(chain.body, keys.pvalue)][keys.id]; - } - // Some datasets, notably the Portal, use a different marker format. - // Coerce it into one that will work with the LDServer API. (CHROM:POS_REF/ALT) - const REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/; - const match = refVar && refVar.match(REGEX_MARKER); - - if (!match) { - throw new Error('Could not request LD for a missing or incomplete marker format'); - } - const [original, chrom, pos, ref, alt] = match; - // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing - // a partial match at most leaves room for potential future features. - let refVar_formatted = `${chrom}:${pos}`; - if (ref && alt) { - refVar_formatted += `_${ref}/${alt}`; - } - - return [refVar_formatted, original]; - } - - /** - * Identify (or guess) the LD reference variant, then add query parameters to the URL to construct a query for the specified region - */ - getURL(state, chain, fields) { - // The LD source/pop can be overridden from plot.state for dynamic layouts - const build = state.genome_build || this.params.build || 'GRCh37'; // This isn't expected to change after the data is plotted. - let source = state.ld_source || this.params.source || '1000G'; - const population = state.ld_pop || this.params.population || 'ALL'; // LDServer panels will always have an ALL - const method = this.params.method || 'rsquare'; - - if (source === '1000G' && build === 'GRCh38') { - // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default. - source = '1000G-FRZ09'; - } - - validateBuildSource(this.constructor.name, build, null); // LD doesn't need to validate `source` option - - const [refVar_formatted, refVar_raw] = this.getRefvar(state, chain, fields); - - // Preserve the user-provided variant spec for use when matching to assoc data - chain.header.ldrefvar = refVar_raw; - - return [ - this.url, 'genome_builds/', build, '/references/', source, '/populations/', population, '/variants', - '?correlation=', method, - '&variant=', encodeURIComponent(refVar_formatted), - '&chrom=', encodeURIComponent(state.chr), - '&start=', encodeURIComponent(state.start), - '&stop=', encodeURIComponent(state.end), - ].join(''); - } - - /** - * The LD adapter caches based on region, reference panel, and population name - * @param state - * @param chain - * @param fields - * @return {string} - */ - getCacheKey(state, chain, fields) { - const base = super.getCacheKey(state, chain, fields); - let source = state.ld_source || this.params.source || '1000G'; - const population = state.ld_pop || this.params.population || 'ALL'; // LDServer panels will always have an ALL - const [refVar, _] = this.getRefvar(state, chain, fields); - return `${base}_${refVar}_${source}_${population}`; - } - - /** - * The LD adapter attempts to intelligently match retrieved LD information to a request for association data earlier in the data chain. - * Since each layer only asks for the data needed for that layer, one LD call is sufficient to annotate many separate association tracks. - */ - combineChainBody(data, chain, fields, outnames, trans) { - let keys = this.findMergeFields(chain); - let reqFields = this.findRequestedFields(fields, outnames); - if (!keys.position) { - throw new Error(`Unable to find position field for merge: ${keys._names_}`); - } - const leftJoin = function (left, right, lfield, rfield) { - let i = 0, j = 0; - while (i < left.length && j < right.position2.length) { - if (left[i][keys.position] === right.position2[j]) { - left[i][lfield] = right[rfield][j]; - i++; - j++; - } else if (left[i][keys.position] < right.position2[j]) { - i++; - } else { - j++; - } - } - }; - const tagRefVariant = function (data, refvar, idfield, outrefname, outldname) { - for (let i = 0; i < data.length; i++) { - if (data[i][idfield] && data[i][idfield] === refvar) { - data[i][outrefname] = 1; - data[i][outldname] = 1; // For label/filter purposes, implicitly mark the ref var as LD=1 to itself - } else { - data[i][outrefname] = 0; - } - } - }; - - // LD servers vary slightly. Some report corr as "rsquare", others as "correlation" - let corrField = data.rsquare ? 'rsquare' : 'correlation'; - leftJoin(chain.body, data, reqFields.ldout, corrField); - if (reqFields.isrefvarin && chain.header.ldrefvar) { - tagRefVariant(chain.body, chain.header.ldrefvar, keys.id, reqFields.isrefvarout, reqFields.ldout); - } - return chain.body; - } - - /** - * The LDServer API is paginated, but we need all of the data to render a plot. Depaginate and combine where appropriate. - */ - fetchRequest(state, chain, fields) { - let url = this.getURL(state, chain, fields); - let combined = { data: {} }; - let chainRequests = function (url) { - return fetch(url).then().then((response) => { - if (!response.ok) { - throw new Error(response.statusText); - } - return response.text(); - }).then(function(payload) { - payload = JSON.parse(payload); - Object.keys(payload.data).forEach(function (key) { - combined.data[key] = (combined.data[key] || []).concat(payload.data[key]); - }); - if (payload.next) { - return chainRequests(payload.next); - } - return combined; - }); - }; - return chainRequests(url); - } -} - /** * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data. * There can be more than one claim per variant; this adapter is written to support a visualization in which each @@ -863,7 +692,7 @@ class LDServer extends BaseApiAdapter { * @public * @see module:LocusZoom_Adapters~BaseApiAdapter */ -class GwasCatalogLZ extends BaseApiAdapter { +class GwasCatalogLZ extends BaseUMAdapter { /** * @param {string} config.url The base URL for the remote data. * @param {Object} config.params @@ -873,100 +702,22 @@ class GwasCatalogLZ extends BaseApiAdapter { * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37. */ constructor(config) { + config.fields = ['rsid', 'trait', 'log_pvalue']; super(config); - this.__dependentSource = true; } /** * Add query parameters to the URL to construct a query for the specified region */ - getURL(state, chain, fields) { - // This is intended to be aligned with another source- we will assume they are always ordered by position, asc - // (regardless of the actual match field) - const build = state.genome_build || this.params.build; - const source = this.params.source; - validateBuildSource(this.constructor.name, build, source); + _getURL(request_options) { + const build = request_options.genome_build || this._config.build; + const source = this._config.source; + this._validateBuildSource(build, source); // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build). // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date const source_query = build ? `&build=${build}` : ` and id eq ${source}`; - return `${this.url }?format=objects&sort=pos&filter=chrom eq '${state.chr}' and pos ge ${state.start} and pos le ${state.end}${source_query}`; - } - - findMergeFields(records) { - // Data from previous sources is already namespaced. Find the alignment field by matching. - const knownFields = Object.keys(records); - // Note: All API endoints involved only give results for 1 chromosome at a time; match is implied - const posMatch = knownFields.find(function (item) { - return item.match(/\b(position|pos)\b/i); - }); - - if (!posMatch) { - throw new Error('Could not find data to align with GWAS catalog results'); - } - return { 'pos': posMatch }; - } - - extractFields (data, fields, outnames, trans) { - // Skip the "individual field extraction" step; extraction will be handled when building chain body instead - return data; - } - - /** - * Intelligently combine the LD data with the association data used for this data layer. See class description - * for a summary of how this works. - */ - combineChainBody(data, chain, fields, outnames, trans) { - if (!data.length) { - return chain.body; - } - - // TODO: Better reuse options in the future. This source is very specifically tied to the UM PortalDev API, where - // the field name is always "log_pvalue". Relatively few sites will write their own gwas-catalog endpoint. - const decider = 'log_pvalue'; - const decider_out = outnames[fields.indexOf(decider)]; - - function leftJoin(left, right, fields, outnames, trans) { // Add `fields` from `right` to `left` - // Add a synthetic, un-namespaced field to all matching records - const n_matches = left['n_catalog_matches'] || 0; - left['n_catalog_matches'] = n_matches + 1; - if (decider && left[decider_out] && left[decider_out] > right[decider]) { - // There may be more than one GWAS catalog entry for the same SNP. This source is intended for a 1:1 - // annotation scenario, so for now it only joins the catalog entry that has the best -log10 pvalue - return; - } - - for (let j = 0; j < fields.length; j++) { - const fn = fields[j]; - const outn = outnames[j]; - - let val = right[fn]; - if (trans && trans[j]) { - val = trans[j](val); - } - left[outn] = val; - } - } - - const chainNames = this.findMergeFields(chain.body[0]); - const catNames = this.findMergeFields(data[0]); - - var i = 0, j = 0; - while (i < chain.body.length && j < data.length) { - var left = chain.body[i]; - var right = data[j]; - - if (left[chainNames.pos] === right[catNames.pos]) { - // There may be multiple catalog entries for each matching SNP; evaluate match one at a time - leftJoin(left, right, fields, outnames, trans); - j += 1; - } else if (left[chainNames.pos] < right[catNames.pos]) { - i += 1; - } else { - j += 1; - } - } - return chain.body; + return `${this._url }?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`; } } @@ -981,38 +732,28 @@ class GwasCatalogLZ extends BaseApiAdapter { * @param {Number} [config.params.source] The ID of the chosen gene dataset. Most usages should omit this parameter and * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37. */ -class GeneLZ extends BaseApiAdapter { +class GeneLZ extends BaseUMAdapter { + constructor(config) { + super(config); + + // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given. + // We will avoid transforming or modifying the payload. + this._validate_fields = false; + this._prefix_namespace = false; + } + /** * Add query parameters to the URL to construct a query for the specified region */ - getURL(state, chain, fields) { - const build = state.genome_build || this.params.build; - let source = this.params.source; - validateBuildSource(this.constructor.name, build, source); + _getURL(request_options) { + const build = request_options.genome_build || this._config.build; + let source = this._config.source; + this._validateBuildSource(build, source); // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build). // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date const source_query = build ? `&build=${build}` : ` and source in ${source}`; - return `${this.url}?filter=chrom eq '${state.chr}' and start le ${state.end} and end ge ${state.start}${source_query}`; - } - - /** - * The UM genes API has a very complex internal data format. Bypass any record parsing, and provide the data layer with - * the exact information returned by the API. (ignoring the fields array in the layout) - * @param data - * @return {Object[]|Object} - */ - normalizeResponse(data) { - return data; - } - - /** - * Does not attempt to namespace or modify the fields from the API payload; the payload format is very complex and - * quite coupled with the data rendering implementation. - * Typically, requests to this adapter specify a single dummy field sufficient to trigger the request: `fields:[ 'gene:all' ]` - */ - extractFields(data, fields, outnames, trans) { - return data; + return `${this._url}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`; } } @@ -1026,7 +767,7 @@ class GeneLZ extends BaseApiAdapter { * @public * @see module:LocusZoom_Adapters~BaseApiAdapter */ -class GeneConstraintLZ extends BaseApiAdapter { +class GeneConstraintLZ extends BaseLZAdapter { /** * @param {string} config.url The base URL for the remote data * @param {Object} config.params @@ -1035,56 +776,54 @@ class GeneConstraintLZ extends BaseApiAdapter { */ constructor(config) { super(config); - this.__dependentSource = true; - } - - /** - * GraphQL API: request details are encoded in the body, not the URL - */ - getURL() { - return this.url; + this._validate_fields = false; + this._prefix_namespace = false; } - /** - * The gnomAD API has a very complex internal data format. Bypass any record parsing, and provide the data layer with - * the exact information returned by the API. - */ - normalizeResponse(data) { - return data; - } - - fetchRequest(state, chain, fields) { - const build = state.genome_build || this.params.build; + _buildRequestOptions(options, genes_data) { + const build = options.genome_build || this._config.build; if (!build) { throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`); } - const unique_gene_names = chain.body.reduce( + const unique_gene_names = new Set(); + for (let gene of genes_data) { // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the // gene names to avoid issuing a malformed GraphQL query. - function (acc, gene) { - acc[gene.gene_name] = null; - return acc; - }, - {} - ); - let query = Object.keys(unique_gene_names).map(function (gene_name) { + unique_gene_names.add(gene.gene_name); + } + + options.query = [...unique_gene_names.values()].map(function (gene_name) { // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268 const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // Each gene symbol is a separate graphQL query, grouped into one request using aliases return `${alias}: gene(gene_symbol: "${gene_name}", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `; }); + options.build = build; + return Object.assign({}, options); + } + + /** + * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps. + */ + _normalizeResponse(response_text) { + const data = JSON.parse(response_text); + return data.data; + } + _performRequest(options) { + let {query, build} = options; if (!query.length || query.length > 25 || build === 'GRCh38') { // Skip the API request when it would make no sense: // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.) - // - Too many genes (gnomAD appears max cost ~25 genes) + // - Too many genes (gnomAD appears to set max cost ~25 genes) // - No genes in region (hence no constraint info) - return Promise.resolve({ data: null }); + return Promise.resolve([]); } - query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas - const url = this.getURL(state, chain, fields); + + const url = this._getURL(options); + // See: https://graphql.org/learn/serving-over-http/ const body = JSON.stringify({ query: query }); const headers = { 'Content-Type': 'application/json' }; @@ -1098,35 +837,6 @@ class GeneConstraintLZ extends BaseApiAdapter { return response.text(); }).catch((err) => []); } - - /** - * Annotate GENCODE data (from a previous request to the genes adapter) with additional gene constraint data from - * the gnomAD API. See class description for a summary of how this works. - */ - combineChainBody(data, chain, fields, outnames, trans) { - if (!data) { - return chain; - } - - chain.body.forEach(function(gene) { - // Find payload keys that match gene names in this response - const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names - const constraint = data[alias] && data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene - if (constraint) { - // Add all fields from constraint data- do not override fields present in the gene source - Object.keys(constraint).forEach(function (key) { - let val = constraint[key]; - if (typeof gene[key] === 'undefined') { - if (typeof val == 'number' && val.toString().includes('.')) { - val = parseFloat(val.toFixed(2)); - } - gene[key] = val; // These two sources are both designed to bypass namespacing - } - }); - } - }); - return chain.body; - } } /** @@ -1140,19 +850,19 @@ class GeneConstraintLZ extends BaseApiAdapter { * @param {Number} [config.params.source] The ID of the chosen dataset. Most usages should omit this parameter and * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37. */ -class RecombLZ extends BaseApiAdapter { +class RecombLZ extends BaseUMAdapter { /** * Add query parameters to the URL to construct a query for the specified region */ - getURL(state, chain, fields) { - const build = state.genome_build || this.params.build; - let source = this.params.source; - validateBuildSource(this.constructor.SOURCE_NAME, build, source); + _getURL(request_options) { + const build = request_options.genome_build || this._config.build; + let source = this._config.source; + this._validateBuildSource(build, source); // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build). // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date const source_query = build ? `&build=${build}` : ` and id in ${source}`; - return `${this.url}?filter=chromosome eq '${state.chr}' and position le ${state.end} and position ge ${state.start}${source_query}`; + return `${this._url}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`; } } @@ -1171,13 +881,14 @@ class RecombLZ extends BaseApiAdapter { * @see module:LocusZoom_Adapters~BaseAdapter * @param {object} data The data to be returned by this source (subject to namespacing rules) */ -class StaticSource extends BaseAdapter { - parseInit(data) { +class StaticSource extends BaseLZAdapter { + constructor(config) { // Does not receive any config; the only argument is the raw data, embedded when source is created - this._data = data; + super(...arguments); + this._data = config.data; } - getRequest(state, chain, fields) { + _performRequest(options) { return Promise.resolve(this._data); } } @@ -1191,124 +902,27 @@ class StaticSource extends BaseAdapter { * @param {String[]} config.params.build This datasource expects to be provided the name of the genome build that will * be used to provide pheWAS results for this position. Note positions may not translate between builds. */ -class PheWASLZ extends BaseApiAdapter { - getURL(state, chain, fields) { - const build = (state.genome_build ? [state.genome_build] : null) || this.params.build; +class PheWASLZ extends BaseUMAdapter { + _getURL(request_options) { + const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build; if (!build || !Array.isArray(build) || !build.length) { - throw new Error(['Adapter', this.constructor.SOURCE_NAME, 'requires that you specify array of one or more desired genome build names'].join(' ')); + throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' ')); } const url = [ - this.url, - "?filter=variant eq '", encodeURIComponent(state.variant), "'&format=objects&", + this._url, + "?filter=variant eq '", encodeURIComponent(request_options.variant), "'&format=objects&", build.map(function (item) { return `build=${encodeURIComponent(item)}`; }).join('&'), ]; return url.join(''); } - - getCacheKey(state, chain, fields) { - // This is not a region-based source; it doesn't make sense to cache by a region - return this.getURL(state, chain, fields); - } -} - -/** - * Base class for "connectors"- this is a highly specialized kind of adapter that is rarely used in most LocusZoom - * deployments. This is meant to be subclassed, rather than used directly. - * - * A connector is a data adapter that makes no server requests and caches no data of its own. Instead, it decides how to - * combine data from other sources in the chain. Connectors are useful when we want to request (or calculate) some - * useful piece of information once, but apply it to many different kinds of record types. - * - * Typically, a subclass will implement the field merging logic in `combineChainBody`. - * - * @public - * @see module:LocusZoom_Adapters~BaseAdapter - */ -class ConnectorSource extends BaseAdapter { - /** - * @param {Object} config.params Additional parameters - * @param {Object} config.params.sources Specify how the hard-coded logic should find the data it relies on in the chain, - * as {internal_name: chain_source_id} pairs. This allows writing a reusable connector that does not need to make - * assumptions about what namespaces a source is using. * - */ - constructor(config) { - super(config); - - if (!config || !config.sources) { - throw new Error('Connectors must specify the data they require as config.sources = {internal_name: chain_source_id}} pairs'); - } - - /** - * Tells the connector how to find the data it relies on - * - * For example, a connector that applies burden test information to the genes layer might specify: - * {gene_ns: "gene", aggregation_ns: "aggregation"} - * - * @member {Object} - * @private - */ - this._source_name_mapping = config.sources; - - // Validate that this source has been told how to find the required information - const specified_ids = Object.keys(config.sources); - /** @property {String[]} Specifies the sources that must be provided in the original config object */ - - this._getRequiredSources().forEach((k) => { - if (!specified_ids.includes(k)) { - // TODO: Fix constructor.name usage in minified bundles - throw new Error(`Configuration for ${this.constructor.name} must specify a source ID corresponding to ${k}`); - } - }); - } - - // Stub- connectors don't have their own url or data, so the defaults don't make sense - parseInit() {} - - getRequest(state, chain, fields) { - // Connectors do not request their own data by definition, but they *do* depend on other sources having been loaded - // first. This method performs basic validation, and preserves the accumulated body from the chain so far. - Object.keys(this._source_name_mapping).forEach((ns) => { - const chain_source_id = this._source_name_mapping[ns]; - if (chain.discrete && !chain.discrete[chain_source_id]) { - throw new Error(`${this.constructor.name} cannot be used before loading required data for: ${chain_source_id}`); - } - }); - return Promise.resolve(chain.body || []); - } - - parseResponse(data, chain, fields, outnames, trans) { - // A connector source does not update chain.discrete, but it may use it. It bypasses data formatting - // and field selection (both are assumed to have been done already, by the previous sources this draws from) - - // Because of how the chain works, connectors are not very good at applying new transformations or namespacing. - // Typically connectors are called with `connector_name:all` in the fields array. - return Promise.resolve(this.combineChainBody(data, chain, fields, outnames, trans)) - .then(function(new_body) { - return {header: chain.header || {}, discrete: chain.discrete || {}, body: new_body}; - }); - } - - combineChainBody(records, chain) { - // Stub method: specifies how to combine the data - throw new Error('This method must be implemented in a subclass'); - } - - /** - * Helper method since ES6 doesn't support class fields - * @private - */ - _getRequiredSources() { - throw new Error('Must specify an array that identifes the kind of data required by this source'); - } } -export { BaseAdapter, BaseApiAdapter }; +export { BaseAdapter, BaseApiAdapter, BaseLZAdapter, BaseUMAdapter }; export { AssociationLZ, - ConnectorSource, GeneConstraintLZ, GeneLZ, GwasCatalogLZ, diff --git a/esm/data/requester.js b/esm/data/requester.js index 47044166..ea37c4a6 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -47,15 +47,15 @@ class Requester { if (!match) { throw new Error(`Invalid namespace name: '${label}'. Should be 'somename' or 'somename(somedep)'`); } - label = match[1] || match[2]; + const entity_label = match[1] || match[2]; const source = this._sources.get(source_name); - if (entities.has(label)) { + if (entities.has(entity_label)) { throw new Error(`Configuration error: within a layer, namespace name '${label}' must be unique`); } - entities.set(label, source); + entities.set(entity_label, source); dependencies.push(label); }); @@ -73,8 +73,10 @@ class Requester { getData(state, namespace_options, join_options) { const [entities, dependencies] = this._config_to_sources(namespace_options, join_options); + if (!dependencies.length) { + return Promise.resolve([]); + } return getLinkedData(state, entities, dependencies, true); - // entities: { assoc: adapter, ld: adapter } } } diff --git a/esm/ext/lz-credible-sets.js b/esm/ext/lz-credible-sets.js index 14e4e880..d8e32e69 100644 --- a/esm/ext/lz-credible-sets.js +++ b/esm/ext/lz-credible-sets.js @@ -34,7 +34,7 @@ import {marking, scoring} from 'gwas-credible-sets'; function install (LocusZoom) { - const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter'); + const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter'); /** * (**extension**) Custom data adapter that calculates the 95% credible set based on provided association data. @@ -43,9 +43,9 @@ function install (LocusZoom) { * @alias module:LocusZoom_Adapters~CredibleSetLZ * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions */ - class CredibleSetLZ extends BaseAdapter { + class CredibleSetLZ extends BaseUMAdapter { /** - * @param {String} config.params.fields.log_pvalue The name of the field containing -log10 pvalue information + * @param {String} config.params.join_fields.log_pvalue The name of the field containing -log10 pvalue information * @param {Number} [config.params.threshold=0.95] The credible set threshold (eg 95%). Will continue selecting SNPs * until the posterior probabilities add up to at least this fraction of the total. * @param {Number} [config.params.significance_threshold=7.301] Do not perform a credible set calculation for this @@ -54,50 +54,53 @@ function install (LocusZoom) { * create a credible set for the entire region; the resulting set may include things below the line of significance. */ constructor(config) { + config.url = true; // FIXME: This is embarrassing super(...arguments); - this.dependentSource = true; // Don't do calcs for a region with no assoc data - } - - parseInit(config) { - super.parseInit(...arguments); - if (!(this.params.fields && this.params.fields.log_pvalue)) { - throw new Error(`Source config for ${this.constructor.SOURCE_NAME} must specify how to find 'fields.log_pvalue'`); + if (!(this._config.join_fields && this._config.join_fields.log_pvalue)) { + throw new Error(`Source config for ${this.constructor.name} must specify how to find 'fields.log_pvalue'`); } // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p) - this.params = Object.assign( + this._config = Object.assign( { threshold: 0.95, significance_threshold: 7.301 }, - this.params + this._config ); + this._prefix_namespace = false; } - getCacheKey (state, chain, fields) { - const threshold = state.credible_set_threshold || this.params.threshold; + _getCacheKey (state) { + const threshold = state.credible_set_threshold || this._config.threshold; return [threshold, state.chr, state.start, state.end].join('_'); } - fetchRequest(state, chain) { - if (!chain.body.length) { + _buildRequestOptions(options, assoc_data) { + const base = super._buildRequestOptions(...arguments); + base._assoc_data = assoc_data; + console.log('hi'); + return base; + } + + _performRequest(options) { + const {_assoc_data} = options; + console.log('calculating'); + if (!_assoc_data.length) { // No credible set can be calculated because there is no association data for this region return Promise.resolve([]); } - const self = this; - // The threshold can be overridden dynamically via `plot.state`, or set when the source is created - const threshold = state.credible_set_threshold || this.params.threshold; + const threshold = this._config.threshold; // Calculate raw bayes factors and posterior probabilities based on information returned from the API - if (typeof chain.body[0][self.params.fields.log_pvalue] === 'undefined') { + if (typeof _assoc_data[0][this._config.join_fields.log_pvalue] === 'undefined') { throw new Error('Credible set source could not locate the required fields from a previous request.'); } - const nlogpvals = chain.body.map((item) => item[self.params.fields.log_pvalue]); + const nlogpvals = _assoc_data.map((item) => item[this._config.join_fields.log_pvalue]); - if (!nlogpvals.some((val) => val >= self.params.significance_threshold)) { + if (!nlogpvals.some((val) => val >= this._config.significance_threshold)) { // If NO points have evidence of significance, define the credible set to be empty // (rather than make a credible set that we don't think is meaningful) return Promise.resolve([]); } - const credset_data = []; try { const scores = scoring.bayesFactors(nlogpvals); const posteriorProbabilities = scoring.normalizeProbabilities(scores); @@ -109,32 +112,16 @@ function install (LocusZoom) { const credSetBool = marking.markBoolean(credibleSet); // Annotate each response record based on credible set membership - for (let i = 0; i < chain.body.length; i++) { - credset_data.push({ - posterior_prob: posteriorProbabilities[i], - contrib_fraction: credSetScaled[i], - is_member: credSetBool[i], - }); + for (let i = 0; i < _assoc_data.length; i++) { + _assoc_data[i][`${options._provider_name}.posterior_prob`] = posteriorProbabilities[i]; + _assoc_data[i][`${options._provider_name}.contrib_fraction`] = credSetScaled[i]; + _assoc_data[i][`${options._provider_name}.is_member`] = credSetBool[i]; } } catch (e) { // If the calculation cannot be completed, return the data without annotation fields console.error(e); } - return Promise.resolve(credset_data); - } - - combineChainBody(data, chain, fields, outnames, trans) { - // At this point namespacing has been applied; add the calculated fields for this source to the chain - if (chain.body.length && data.length) { - for (let i = 0; i < data.length; i++) { - const src = data[i]; - const dest = chain.body[i]; - Object.keys(src).forEach(function (attr) { - dest[attr] = src[attr]; - }); - } - } - return chain.body; + return Promise.resolve(JSON.stringify(_assoc_data)); } } @@ -163,7 +150,6 @@ function install (LocusZoom) { * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions */ const annotation_credible_set_tooltip = { - namespace: { 'assoc': 'assoc', 'credset': 'credset' }, closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, @@ -184,7 +170,7 @@ function install (LocusZoom) { const base = LocusZoom.Layouts.get('data_layer', 'association_pvalues', { unnamespaced: true, id: 'associationcredibleset', - namespace: { 'assoc': 'assoc', 'credset': 'credset', 'ld': 'ld' }, + namespace: { 'assoc': 'assoc', 'credset(assoc)': 'credset', 'ld(assoc)': 'ld' }, fill_opacity: 0.7, tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set', { unnamespaced: true }), fields: [ @@ -216,7 +202,7 @@ function install (LocusZoom) { * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions */ const annotation_credible_set_layer = { - namespace: { 'assoc': 'assoc', 'credset': 'credset' }, + namespace: { 'assoc': 'assoc', 'credset(assoc)': 'credset' }, id: 'annotationcredibleset', type: 'annotation_track', id_field: 'assoc.variant', @@ -297,7 +283,6 @@ function install (LocusZoom) { const l = LocusZoom.Layouts.get('panel', 'association', { unnamespaced: true, id: 'associationcrediblesets', - namespace: { 'assoc': 'assoc', 'credset': 'credset' }, data_layers: [ LocusZoom.Layouts.get('data_layer', 'significance', { unnamespaced: true }), LocusZoom.Layouts.get('data_layer', 'recomb_rate', { unnamespaced: true }), diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index 115fac15..893e3969 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -43,7 +43,7 @@ const YCE = Symbol.for('lzYCE'); function install (LocusZoom) { - const BaseApiAdapter = LocusZoom.Adapters.get('BaseApiAdapter'); + const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter'); const _Button = LocusZoom.Widgets.get('_Button'); const _BaseWidget = LocusZoom.Widgets.get('BaseWidget'); @@ -55,11 +55,11 @@ function install (LocusZoom) { * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions * @param {number} config.params.source The numeric ID for a specific dataset as assigned by the API server */ - class IntervalLZ extends BaseApiAdapter { - getURL(state, chain, fields) { - const source = chain.header.bedtracksource || this.params.source; - const query = `?filter=id in ${source} and chromosome eq '${state.chr}' and start le ${state.end} and end ge ${state.start}`; - return `${this.url}${query}`; + class IntervalLZ extends BaseUMAdapter { + _getURL(request_options) { + const source = this._config.source; + const query = `?filter=id in ${source} and chromosome eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}`; + return `${this._url}${query}`; } } diff --git a/esm/ext/lz-tabix-source.js b/esm/ext/lz-tabix-source.js index 5246d71d..c7daad47 100644 --- a/esm/ext/lz-tabix-source.js +++ b/esm/ext/lz-tabix-source.js @@ -32,7 +32,7 @@ * // Tabix performs region queries. If you are fetching interval data (one end outside the edge of the plot), then * // "overfetching" can help to ensure that data partially outside the view region is retrieved * // If you are fetching single-point data like association summary stats, then overfetching is unnecessary - * params: { overfetch: 0.25 } + * overfetch: 0.25 * }]); * ``` * @@ -42,7 +42,7 @@ import tabix from 'tabix-reader'; function install(LocusZoom) { - const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter'); + const BaseLZAdapter = LocusZoom.Adapters.get('BaseLZAdapter'); /** * Loads data from a remote Tabix file (if the file host has been configured with proper @@ -57,26 +57,27 @@ function install(LocusZoom) { * structured object of data fields * @param {string} config.url_data The URL for the bgzipped and tabix-indexed file * @param {string} [config.url_tbi] The URL for the tabix index. Defaults to `url_data` + '.tbi' - * @param {number} [config.params.overfetch = 0] Optionally fetch more data than is required to satisfy the + * @param {number} [config.overfetch = 0] Optionally fetch more data than is required to satisfy the * region query. (specified as a fraction of the region size, 0-1). * Useful for sources where interesting features might lie near the edges of the plot, eg BED track intervals. */ - class TabixUrlSource extends BaseAdapter { - parseInit(init) { - if (!init.parser_func || !init.url_data) { + class TabixUrlSource extends BaseLZAdapter { + constructor(config) { + config.url = config.url_data; + super(config); + if (!config.parser_func || !config.url_data) { throw new Error('Tabix source is missing required configuration options'); } - this.parser = init.parser_func; + this.parser = config.parser_func; // TODO: In the future, accept a pre-configured reader instance (as an alternative to the URL). Most useful // for UIs that want to validate the tabix file before adding it to the plot, like LocalZoom. - this.url_data = init.url_data; - this.url_tbi = init.url_tbi || `${this.url_data}.tbi`; + this.url_data = config.url_data; + this.url_tbi = config.url_tbi || `${this.url_data}.tbi`; // In tabix mode, sometimes we want to fetch a slightly larger region than is displayed, in case a // feature is on the edge of what the tabix query would return. // Specify overfetch in units of % of total region size. ("fetch 10% extra before and after") - const params = init.params || {}; - this.params = params; + const params = config.params || {}; this._overfetch = params.overfetch || 0; if (this._overfetch < 0 || this._overfetch > 1) { @@ -90,17 +91,17 @@ function install(LocusZoom) { }); } - fetchRequest(state /*, chain, fields */) { + _performRequest(options) { return new Promise((resolve, reject) => { // Ensure that the reader is fully created (and index available), then make a query - const region_start = state.start; - const region_end = state.end; + const region_start = options.start; + const region_end = options.end; const extra_amount = this._overfetch * (region_end - region_start); - const start = state.start - extra_amount; - const end = state.end + extra_amount; + const start = options.start - extra_amount; + const end = options.end + extra_amount; this._reader_promise.then((reader) => { - reader.fetch(state.chr, start, end, function (data, err) { + reader.fetch(options.chr, start, end, function (data, err) { if (err) { reject(new Error('Could not read requested region. This may indicate an error with the .tbi index.')); } @@ -110,9 +111,9 @@ function install(LocusZoom) { }); } - normalizeResponse(data) { + _normalizeResponse(records) { // Parse the data from lines of text to objects - return data.map(this.parser); + return records.map(this.parser); } } diff --git a/esm/layouts/index.js b/esm/layouts/index.js index 20cf9dca..cb0c9a68 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -30,7 +30,7 @@ const standard_association_tooltip = { html: `{{assoc.variant|htmlescape}}
P Value: {{assoc.log_pvalue|logtoscinotation|htmlescape}}
Ref. Allele: {{assoc.ref_allele|htmlescape}}
- {{#if ld.isrefvar}}LD Reference Variant{{#else}} + {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}} { + if (!assoc_data.length) { + return assoc_data; + } + + // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each + const catalog_by_variant = joins.groupBy(catalog_data, catalog_key); + + const catalog_flat = []; // Store only the top significant claim for each catalog variant entry + for (let claims of catalog_by_variant.values()) { + // Find max item within this set of claims, push that to catalog_ + let best = 0; + let best_variant; + for (let item of claims) { + if (item[catalog_logp_name] >= best) { + best_variant = item; + } + } + best_variant.n_catalog_matches = claims.length; + catalog_flat.push(best_variant); + } + return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key); +}); + +// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them. +registry.add('genes_to_gnomad_constraint', (genes_data, constraint_data) => { + genes_data.forEach(function(gene) { + // Find payload keys that match gene names in this response + const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names + const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene + if (constraint) { + // Add all fields from constraint data- do not override fields present in the gene source + Object.keys(constraint).forEach(function (key) { + let val = constraint[key]; + if (typeof gene[key] === 'undefined') { + if (typeof val == 'number' && val.toString().includes('.')) { + val = parseFloat(val.toFixed(2)); + } + gene[key] = val; // These two sources are both designed to bypass namespacing + } + }); + } + }); + return genes_data; +}); + export default registry; diff --git a/examples/coaccessibility.html b/examples/coaccessibility.html index 896eb823..8a93d220 100644 --- a/examples/coaccessibility.html +++ b/examples/coaccessibility.html @@ -84,9 +84,9 @@
< return home
// Define LocusZoom Data Sources object differently depending on online status var apiBase = "https://portaldev.sph.umich.edu/api/v1/"; data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", params: { source: 45, id_field: "variant" }}]) - .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", params: { source: '1000G', build: 'GRCh37', population: 'ALL' } }]) - .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", params: { build: 'GRCh37' } }]) + .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", source: 45, id_field: "variant" }]) + .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', build: 'GRCh37', population: 'ALL' }]) + .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) .add("access", ["TabixUrlSource", { // Corresponds to: https://www.diabetesepigenome.org/files/DFF044MQE/ // as processed from https://www.diabetesepigenome.org/annotations/DSR299XDW/ @@ -95,10 +95,10 @@
< return home
parser_func: dega_bed_parser, // We are fetching two elements, but only one of them is captured in the tabix index. // Fetching 25% extra data on each side can ensure that things near the edge are returned when we query. - params: { overfetch: 0.25 } + overfetch: 0.25, }]) - .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", params: { build: 'GRCh37' } }]) - .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) + .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); // Get the standard association plot layout from LocusZoom's built-in layouts var stateUrlMapping = { chr: "chrom", start: "start", end: "end" }; diff --git a/examples/credible_sets.html b/examples/credible_sets.html index 93231bea..14a47d4a 100644 --- a/examples/credible_sets.html +++ b/examples/credible_sets.html @@ -89,13 +89,14 @@

Top Hits

data_sources = new LocusZoom.DataSources() .add("assoc", ["AssociationLZ", { url: apiBase + "statistic/single/", - params: { source: 45, id_field: "variant" } + source: 45, + id_field: "variant", }]) - .add("credset", ["CredibleSetLZ", { params: { fields: { log_pvalue: 'assoc:log_pvalue' }, threshold: 0.95, significance_threshold: 7.301 } }]) - .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", params: { source: '1000G', build: 'GRCh37', population: 'ALL' } }]) - .add("gene", ["GeneLZ", {url: apiBase + "annotation/genes/", params: { build: 'GRCh37' }}]) - .add("recomb", ["RecombLZ", {url: apiBase + "annotation/recomb/results/", params: { build: 'GRCh37' }}]) - .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("credset", ["CredibleSetLZ", { join_fields: { log_pvalue: 'assoc.log_pvalue' }, threshold: 0.95, significance_threshold: 7.301 }]) + .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', build: 'GRCh37', population: 'ALL' }]) + .add("gene", ["GeneLZ", {url: apiBase + "annotation/genes/", build: 'GRCh37' }]) + .add("recomb", ["RecombLZ", {url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) + .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); /* Define and render the plot diff --git a/examples/gwas_catalog.html b/examples/gwas_catalog.html index b4fa8f2f..b52f4da4 100644 --- a/examples/gwas_catalog.html +++ b/examples/gwas_catalog.html @@ -85,12 +85,12 @@

Top Hits

*/ var apiBase = "//portaldev.sph.umich.edu/api/v1/"; var data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", { url: apiBase + "statistic/single/", params: { source: 45, id_field: "variant" } }]) - .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", params: { source: '1000G', population: 'ALL' } }]) + .add("assoc", ["AssociationLZ", { url: apiBase + "statistic/single/", source: 45, id_field: "variant" }]) + .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', population: 'ALL' }]) .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/" }]) .add("catalog", ["GwasCatalogLZ", { url: apiBase + 'annotation/gwascatalog/results/' }]) .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/" }]) - .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); /* Define and render the plot diff --git a/examples/interval_annotations.html b/examples/interval_annotations.html index 4bf3fbb4..e24cd64a 100644 --- a/examples/interval_annotations.html +++ b/examples/interval_annotations.html @@ -66,12 +66,12 @@

Top Hits

// Define Data Sources var apiBase = "https://portaldev.sph.umich.edu/api/v1/"; var data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", params: { source: 45, id_field: "variant" }}]) - .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", params: { source: '1000G', build: 'GRCh37', population: 'ALL' } }]) - .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", params: { build: 'GRCh37' } }]) - .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", params: { build: 'GRCh37' } }]) - .add("intervals", ["IntervalLZ", { url: apiBase + "annotation/intervals/results/", params: {source: 19} }]) - .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", source: 45, id_field: "variant" }]) + .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', build: 'GRCh37', population: 'ALL' }]) + .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) + .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) + .add("intervals", ["IntervalLZ", { url: apiBase + "annotation/intervals/results/", source: 19 }]) + .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); // Get the standard assocation plot layout from LocusZoom's built-in layouts layout = LocusZoom.Layouts.get("plot", "interval_association"); diff --git a/examples/interval_enrichment.html b/examples/interval_enrichment.html index b5379c94..4dc73921 100644 --- a/examples/interval_enrichment.html +++ b/examples/interval_enrichment.html @@ -64,13 +64,13 @@
< return home
// Define Data Sources var apiBase = "https://portaldev.sph.umich.edu/api/v1/"; var data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", params: { source: 45, id_field: "variant" }}]) - .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", params: { source: '1000G', build: 'GRCh37', population: 'ALL' } }]) - .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", params: { build: 'GRCh37' } }]) - .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", params: { build: 'GRCh37' } }]) + .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", source: 45, id_field: "variant" }]) + .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', build: 'GRCh37', population: 'ALL' }]) + .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) + .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) // There's no special transformation being done to intervals data- just fetch from a URL .add("intervals", ["BaseApiAdapter", { url: 'data/intervals_enrichment_simplified.json' }]) - .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); // Get the standard assocation plot layout from LocusZoom's built-in layouts const layout = LocusZoom.Layouts.get("plot", "intervals_association_enrichment"); diff --git a/examples/js/aggregation-tests-example-page.js b/examples/js/aggregation-tests-example-page.js index a40771f2..a1458c78 100644 --- a/examples/js/aggregation-tests-example-page.js +++ b/examples/js/aggregation-tests-example-page.js @@ -192,23 +192,23 @@ function createDisplayWidgets(label_store, context) { .add('aggregation', ['AggregationTestSourceLZ', { url: 'https://portaldev.sph.umich.edu/raremetal/v1/aggregation/covariance' }]) .add('assoc', ['AssocFromAggregationLZ', { // Use a special source that restructures already-fetched data from: 'aggregation', - params: { id_field: 'variant' }, + id_field: 'variant', }]) .add('ld', ['LDServer', { url: 'https://portaldev.sph.umich.edu/ld/', - params: { source: '1000G', build: 'GRCh37', population: 'ALL' }, + source: '1000G', build: 'GRCh37', population: 'ALL', }]) - .add('gene', ['GeneLZ', { url: apiBase + 'annotation/genes/', params: { build: 'GRCh37' } }]) + .add('gene', ['GeneLZ', { url: apiBase + 'annotation/genes/', build: 'GRCh37' }]) .add('aggregation_genes', ['GeneAggregationConnectorLZ', { sources: { aggregation_ns: 'aggregation', gene_ns: 'gene', }, }]) - .add('recomb', ['RecombLZ', { url: apiBase + 'annotation/recomb/results/', params: { build: 'GRCh37' } }]) + .add('recomb', ['RecombLZ', { url: apiBase + 'annotation/recomb/results/', build: 'GRCh37' }]) .add('constraint', ['GeneConstraintLZ', { url: 'https://gnomad.broadinstitute.org/api/', - params: { build: 'GRCh37' }, + build: 'GRCh37', }]); const stateUrlMapping = { chr: 'chrom', start: 'start', end: 'end' }; diff --git a/examples/misc/covariates_model.html b/examples/misc/covariates_model.html index 0138d2d6..3c214782 100644 --- a/examples/misc/covariates_model.html +++ b/examples/misc/covariates_model.html @@ -67,11 +67,11 @@

Top Hits

// Define Data Sources var apiBase = "https://portaldev.sph.umich.edu/api/v1/"; var data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", params: { source: 45, id_field: "variant" }}]) - .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", params: { source: '1000G', build: 'GRCh37', population: 'ALL' } }]) - .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", params: { build: 'GRCh37' } }]) - .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", params: { build: 'GRCh37' } }]) - .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", source: 45, id_field: "variant" }]) + .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', build: 'GRCh37', population: 'ALL' }]) + .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) + .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) + .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); // Get the standard assocation plot layout from LocusZoom's built-in layouts var mods = { diff --git a/examples/multiple_phenotypes_layered.html b/examples/multiple_phenotypes_layered.html index 4b8193cd..23d71b03 100644 --- a/examples/multiple_phenotypes_layered.html +++ b/examples/multiple_phenotypes_layered.html @@ -69,9 +69,9 @@

Top Hits

// Define base data sources var apiBase = "https://portaldev.sph.umich.edu/api/v1/"; var data_sources = new LocusZoom.DataSources() - .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", params: { build: 'GRCh37' } }]) - .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", params: { build: 'GRCh37' } }]) - .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) + .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) + .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); // Build the base layout var association_panel_mods = { @@ -104,9 +104,11 @@

Top Hits

{ namespace: "cholesterol", title: "Total cholesterol meta-analysis", color: "rgb(53, 126, 189)", study_id: 30 } ]; phenos.forEach(function(pheno){ - data_sources.add(pheno.namespace, ["AssociationLZ", {url: apiBase + "statistic/single/", params: { source: pheno.study_id, id_field: "variant" }}]); + data_sources.add(pheno.namespace, ["AssociationLZ", {url: apiBase + "statistic/single/", source: pheno.study_id, id_field: "variant" }]); var association_data_layer_mods = { + // FIXME: implement dependency fetcher so that this demo doesn't pull LD when it shouldn't namespace: { "assoc": pheno.namespace }, + join_options: [], id: "associationpvalues_" + pheno.namespace, name: pheno.title, point_shape: "circle", @@ -115,7 +117,6 @@

Top Hits

legend: [ { shape: "circle", color: pheno.color, size: 40, label: pheno.title, class: "lz-data_layer-scatter" }, ], - fields: [pheno.namespace+":variant", pheno.namespace+":position", pheno.namespace+":log_pvalue", pheno.namespace+":log_pvalue|logtoscinotation", pheno.namespace+":ref_allele"], tooltip: { closable: true, show: { or: ["highlighted", "selected"] }, diff --git a/examples/phewas_forest.html b/examples/phewas_forest.html index 6062180f..c98e5745 100644 --- a/examples/phewas_forest.html +++ b/examples/phewas_forest.html @@ -55,7 +55,7 @@
< return home
// TODO: Write a real data source to fetch and parse data from an API in the future. For now this fetches a // JSON file with a sample payload. var data_sources = new LocusZoom.DataSources() - .add("phewas", ["PheWASLZ", { url: "data/phewas_forest.json", params: { build: ["GRCh37"] } } ]); + .add("phewas", ["PheWASLZ", { url: "data/phewas_forest.json", build: ["GRCh37"] } ]); // Define a reusable layout, and then retrieve it so that the namespaces get filled in LocusZoom.Layouts.add("plot", "phewas_forest", { @@ -102,7 +102,7 @@
< return home
point_shape: "square", point_size: { scale_function: "interpolate", - field: "{{namespace[phewas]}}log_pvalue", + field: "phewas.log_pvalue", parameters: { breaks: [0, 10], values: [60, 160], @@ -111,7 +111,7 @@
< return home
}, color: { scale_function: "interpolate", - field: "{{namespace[phewas]}}log_pvalue", + field: "phewas.log_pvalue", parameters: { breaks: [0, 10], values: ["#fee8c8","#b30000"], @@ -127,21 +127,21 @@
< return home
{ shape: "square", class: "lz-data_layer-forest", color: "#d7301f", size: 140, label: "8 - 10" }, { shape: "square", class: "lz-data_layer-forest", color: "#b30000", size: 160, label: "10+" } ], - id_field: "{{namespace[phewas]}}phenotype", - fields: ["{{namespace[phewas]}}phenotype", "{{namespace[phewas]}}log_pvalue", "{{namespace[phewas]}}log_pvalue|logtoscinotation", "{{namespace[phewas]}}beta", "{{namespace[phewas]}}ci_start", "{{namespace[phewas]}}ci_end"], + id_field: "phewas.phenotype", + fields: ["phewas.phenotype", "phewas.log_pvalue", "phewas.log_pvalue|logtoscinotation", "phewas.beta", "phewas.ci_start", "phewas.ci_end"], x_axis: { - field: "{{namespace[phewas]}}beta", + field: "phewas.beta", lower_buffer: 0.1, upper_buffer: 0.1 }, y_axis: { axis: 2, - category_field: "{{namespace[phewas]}}phenotype", // Labels - field: "{{namespace[phewas]}}y_offset", // Positions (dynamically added) + category_field: "phewas.phenotype", // Labels + field: "phewas.y_offset", // Positions (dynamically added) }, confidence_intervals: { - start_field: "{{namespace[phewas]}}ci_start", - end_field: "{{namespace[phewas]}}ci_end" + start_field: "phewas.ci_start", + end_field: "phewas.ci_end" }, behaviors: { onmouseover: [ @@ -162,10 +162,10 @@
< return home
closable: true, show: { or: ["highlighted", "selected"] }, hide: { and: ["unhighlighted", "unselected"] }, - html: "{{{{namespace[phewas]}}phenotype|htmlescape}}
" - + "P Value: {{{{namespace[phewas]}}log_pvalue|logtoscinotation|htmlescape}}
" - + "Odds Ratio: {{{{namespace[phewas]}}beta|htmlescape}}
" - + "95% Conf. Interval: [ {{{{namespace[phewas]}}ci_start|htmlescape}} {{{{namespace[phewas]}}ci_end|htmlescape}} ]" + html: "{{phewas.phenotype|htmlescape}}
" + + "P Value: {{phewas.log_pvalue|logtoscinotation|htmlescape}}
" + + "Odds Ratio: {{phewas.beta|htmlescape}}
" + + "95% Conf. Interval: [ {{phewas.ci_start|htmlescape}} {{phewas.ci_end|htmlescape}} ]" } }, { diff --git a/examples/phewas_scatter.html b/examples/phewas_scatter.html index 06e58c42..73c7efb6 100644 --- a/examples/phewas_scatter.html +++ b/examples/phewas_scatter.html @@ -195,19 +195,19 @@

API (for developers)

dataSources .add("phewas", ["PheWASLZ", { url: "https://portaldev.sph.umich.edu/" + "api/v1/statistic/phewas/", - params: { build: ["GRCh37"] } + build: ["GRCh37"] }]) - .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", params: { build: 'GRCh37' } }]) - .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) + .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); } else { apiBase = window.location.origin + window.location.pathname.substr(0, window.location.pathname.lastIndexOf("/") + 1) + "data/"; dataSources .add("phewas", ["PheWASLZ", { url: apiBase + "phewas_" + String(variantChrom) + "_" + String(variantPosition) + "_C-T.json", - params: { build: ["GRCh37"] } + build: ["GRCh37"] }]) - .add("gene", ["GeneLZ", { url: apiBase + "genes_10_114508349-115008349.json", params: { build: 'GRCh37' } }]) - .add("constraint", ["GeneConstraintLZ", { url: apiBase + "gene_constraints_10_114508349-115008349.json", params: { build: 'GRCh37' } }]); + .add("gene", ["GeneLZ", { url: apiBase + "genes_10_114508349-115008349.json", build: 'GRCh37' }]) + .add("constraint", ["GeneConstraintLZ", { url: apiBase + "gene_constraints_10_114508349-115008349.json", build: 'GRCh37' }]); } // Define the layout diff --git a/index.html b/index.html index 71529cc9..c45b4912 100644 --- a/index.html +++ b/index.html @@ -202,16 +202,16 @@
Multiple Phenotypes (Layered)
if (online) { apiBase = "https://portaldev.sph.umich.edu/api/v1/"; data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", {base_url: apiBase + "statistic/single/", source: 45 }]); - // .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", params: { source: '1000G', build: 'GRCh37', population: 'ALL' } }]) - // .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", params: { build: 'GRCh37' } }]) - // .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", params: { build: 'GRCh37' } }]) - // .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", params: { build: 'GRCh37' } }]); + .add("assoc", ["AssociationLZ", { url: apiBase + "statistic/single/", source: 45 }]) + .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', build: 'GRCh37', population: 'ALL' }]) + .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) + .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) + .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); } else { apiBase = window.location.origin + window.location.pathname.substr(0, window.location.pathname.lastIndexOf("/") + 1) + "examples/data/"; data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", {base_url: apiBase + "assoc_10_114550452-115067678.json?", source: 45 }]) - .add("ld", ["LDServer", { url: apiBase + "ld_10_114550452-115067678.json?" , params: { build: 'GRCh37' }}]) + .add("assoc", ["AssociationLZ", { url: apiBase + "assoc_10_114550452-115067678.json?", source: 45 }]) + .add("ld", ["LDServer", { url: apiBase + "ld_10_114550452-115067678.json?", build: 'GRCh37' }]) .add("gene", ["GeneLZ", { url: apiBase + "genes_10_114550452-115067678.json?", params: { build: 'GRCh37' } }]) .add("recomb", ["RecombLZ", { url: apiBase + "recomb_10_114550452-115067678.json?", params: { build: 'GRCh37' } }]) .add("constraint", ["GeneConstraintLZ", { url: apiBase + "constraint_10_114550452-115067678.json?", params: { build: 'GRCh37' } }]); From 1f3cf9aed84a63c7072b7936e1b56e94eeee240b Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Sat, 4 Sep 2021 23:35:54 -0400 Subject: [PATCH 005/100] Implement smarter subset-of-region caching with new LRU --- esm/data/adapters.js | 80 ++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 07f44122..cab049db 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -52,6 +52,22 @@ class BaseLZAdapter extends BaseUrlAdapter { this._prefix_namespace = (typeof prefix_namespace === 'boolean') ? prefix_namespace : true; } + _getCacheKey(options) { + // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default + let {chr, start, end} = options; // Current view: plot.state + + // Does a prior cache hit qualify as a superset of the ROI? + const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end); + if (superset) { + ({ chr, start, end } = superset.metadata); + } + + // The default cache key is region-based, and this method only returns the region-based part of the cache hit + // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check + options._cache_meta = { chr, start, end }; + return `${chr}_${start}_${end}`; + } + /** * Note: since namespacing is the last thing we usually want to do, calculations will want to call super AFTER their own code. * @param records @@ -59,7 +75,7 @@ class BaseLZAdapter extends BaseUrlAdapter { * @returns {*} * @private */ - _annotateRecords(records, options) { + _postProcessResponse(records, options) { if (!this._prefix_namespace) { return records; } @@ -186,33 +202,30 @@ class LDServer extends BaseUMAdapter { } _getURL(request_options) { - // The LD source/pop can be overridden from plot.state for dynamic layouts - const build = request_options.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted. - let source = request_options.ld_source || this._config.source || '1000G'; - const population = request_options.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL const method = this._config.method || 'rsquare'; - - if (source === '1000G' && build === 'GRCh38') { - // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default. - source = '1000G-FRZ09'; - } - - this._validateBuildSource(build, null); // LD doesn't need to validate `source` option - - const refvar = request_options.ld_refvar; + const { + chr, start, end, + ld_refvar, + genome_build, ld_source, ld_population, + } = request_options; return [ - this._url, 'genome_builds/', build, '/references/', source, '/populations/', population, '/variants', + this._url, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants', '?correlation=', method, - '&variant=', encodeURIComponent(refvar), - '&chrom=', encodeURIComponent(request_options.chr), - '&start=', encodeURIComponent(request_options.start), - '&stop=', encodeURIComponent(request_options.end), + '&variant=', encodeURIComponent(ld_refvar), + '&chrom=', encodeURIComponent(chr), + '&start=', encodeURIComponent(start), + '&stop=', encodeURIComponent(end), ].join(''); } _buildRequestOptions(state, assoc_data) { - // If no state refvar is provided, find the most significant variant in any provided assoc data. Assumes that assoc satisfies the "assoc" fields contract, eg has fields variant and log_pvalue + if (!assoc_data) { + throw new Error('LD request must depend on association data'); + } + + // If no state refvar is provided, find the most significant variant in any provided assoc data. + // Assumes that assoc satisfies the "assoc" fields contract, eg has fields variant and log_pvalue const base = super._buildRequestOptions(...arguments); if (!assoc_data.length) { base._skip_request = true; @@ -227,7 +240,6 @@ class LDServer extends BaseUMAdapter { let best_hit = {}; if (state.ldrefvar) { // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data - // FIXME: mark Ld refvar for top hits. What if assoc format is different than how we talk to the server? refvar = state.ldrefvar; best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {}; } else { @@ -263,13 +275,26 @@ class LDServer extends BaseUMAdapter { } base.ld_refvar = refvar; - return base; + + // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config + const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted. + let ld_source = state.ld_source || this._config.source || '1000G'; + const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL + + if (ld_source === '1000G' && genome_build === 'GRCh38') { + // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default. + ld_source = '1000G-FRZ09'; + } + + this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option + return Object.assign({}, base, { genome_build, ld_source, ld_population }); } _getCacheKey(options) { - // LD is keyed by more than just region; use full URL as cache key - // TODO: Support multiregion cache calls - return this._getURL(options); + // LD is keyed by more than just region; append other parameters to the base cache key + const base = super._getCacheKey(options); + const { ld_refvar, ld_source, ld_population } = options; + return `${base}_${ld_refvar}_${ld_source}_${ld_population}`; } _performRequest(options) { @@ -917,6 +942,11 @@ class PheWASLZ extends BaseUMAdapter { ]; return url.join(''); } + + _getCacheKey(options) { + // Not a region based source; don't do anything smart for cache check + return this._getURL(options); + } } export { BaseAdapter, BaseApiAdapter, BaseLZAdapter, BaseUMAdapter }; From 491de358f34c4e05d605ebad93f2729b8011f80c Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Sat, 4 Sep 2021 23:36:24 -0400 Subject: [PATCH 006/100] Fix template parser to respect new `namespace.fieldname` syntax [ci skip] --- esm/helpers/display.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esm/helpers/display.js b/esm/helpers/display.js index 91f461ec..2d0207c5 100644 --- a/esm/helpers/display.js +++ b/esm/helpers/display.js @@ -160,7 +160,7 @@ function parseFields(html, data, extra) { // `tokens` is like [token,...] // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'} const tokens = []; - const regex = /{{(?:(#if )?([A-Za-z0-9_:|]+)|(#else)|(\/if))}}/; + const regex = /{{(?:(#if )?([A-Za-z0-9_.|]+)|(#else)|(\/if))}}/; while (html.length > 0) { const m = regex.exec(html); if (!m) { From 2dcb3bffa7574d1f6e5b821daf3ea3b610f17668 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Sun, 5 Sep 2021 14:13:54 -0400 Subject: [PATCH 007/100] Simplify validation of URLs and use base cache behavior to avoid mutable object references [ci skip] --- esm/components/plot.js | 2 +- esm/data/adapters.js | 420 ++++------------------------------ esm/ext/lz-credible-sets.js | 5 +- esm/ext/lz-intervals-track.js | 4 +- esm/ext/lz-tabix-source.js | 1 - 5 files changed, 44 insertions(+), 388 deletions(-) diff --git a/esm/components/plot.js b/esm/components/plot.js index ee266be6..90fdf522 100644 --- a/esm/components/plot.js +++ b/esm/components/plot.js @@ -648,7 +648,7 @@ class Plot { // Register an event listener that is notified whenever new data has been rendered const error_callback = opts.onerror || function (err) { - console.log('An error occurred while acting on an external callback', err); + console.error('An error occurred while acting on an external callback', err); }; const listener = () => { diff --git a/esm/data/adapters.js b/esm/data/adapters.js index cab049db..432c32da 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -22,8 +22,8 @@ * * ``` * const data_sources = new LocusZoom.DataSources(); - * data_sources.add("trait1", ["AssociationLZ", {url: "http://server.com/api/single/", params: {source: 1}}]); - * data_sources.add("trait2", ["AssociationLZ", {url: "http://server.com/api/single/", params: {source: 2}}]); + * data_sources.add("trait1", ["AssociationLZ", { url: "http://server.com/api/single/", source: 1 }]); + * data_sources.add("trait2", ["AssociationLZ", { url: "http://server.com/api/single/", source: 2 }]); * ``` * * These data sources are then passed to the plot when data is to be rendered: @@ -185,15 +185,14 @@ class AssociationLZ extends BaseUMAdapter { this._source_id = source; } - _getURL (state) { - const {chr, start, end} = state; - return `${this._url}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`; + _getURL (request_options) { + const {chr, start, end} = request_options; + const base = super._getURL(request_options); + return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`; } } - - class LDServer extends BaseUMAdapter { constructor(config) { // item1 = refvar, item2 = othervar @@ -209,8 +208,10 @@ class LDServer extends BaseUMAdapter { genome_build, ld_source, ld_population, } = request_options; + const base = super._getURL(request_options); + return [ - this._url, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants', + base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants', '?correlation=', method, '&variant=', encodeURIComponent(ld_refvar), '&chrom=', encodeURIComponent(chr), @@ -323,8 +324,7 @@ class LDServer extends BaseUMAdapter { return combined; }); }; - return chainRequests(url) - .then((response) => JSON.stringify(response)); + return chainRequests(url); } } @@ -334,350 +334,9 @@ class LDServer extends BaseUMAdapter { // methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the // private API methods exist in the base class. -/** - * Base class for LocusZoom data sources (any). See also: BaseApiAdapter for requests from a remote URL. - * @public - */ class BaseAdapter { - /** - * @param {object} config Configuration options - */ - constructor(config) { - /** - * Whether to enable caching (true for most data adapters) - * @private - * @member {Boolean} - */ - this._enableCache = true; - this._cachedKey = null; - - // Almost all LZ sources are "region based". Cache the region requested and use it to determine whether - // the cache would satisfy the request. - this._cache_pos_start = null; - this._cache_pos_end = null; - - /** - * Whether this adapter type is dependent on previous requests- for example, the LD source cannot annotate - * association data if no data was found for that region. - * @private - * @member {boolean} - */ - this.__dependentSource = false; - - // Parse configuration options - this.parseInit(config); - } - - /** - * Parse configuration used to create the instance. Many custom sources will override this method to suit their - * needs (eg specific config options, or for sources that do not retrieve data from a URL) - * @protected - * @param {String|Object} config Basic configuration- either a url, or a config object - * @param {String} [config.url] The datasource URL - * @param {String} [config.params] Initial config params for the datasource - */ - parseInit(config) { - /** - * @private - * @member {Object} - */ - this.params = config.params || {}; - } - - /** - * A unique identifier that indicates whether cached data is valid for this request. For most sources using GET - * requests to a REST API, this is usually the region requested. Some sources will append additional params to define the request. - * - * This means that to change caching behavior, both the URL and the cache key may need to be updated. However, - * it allows most datasources to skip an extra network request when zooming in. - * @protected - * @param {Object} state Information available in plot.state (chr, start, end). Sometimes used to inject globally - * available information that influences the request being made. - * @param {Object} chain The data chain from previous requests made in a sequence. - * @param fields - * @returns {String} - */ - getCacheKey(state, chain, fields) { - // Most region sources, by default, will cache the largest region that satisfies the request: zooming in - // should be satisfied via the cache, but pan or region change operations will cause a network request - - // Some adapters rely on values set in chain.header during the getURL call. (eg, the LD source uses - // this to find the LD refvar) Calling this method is a backwards-compatible way of ensuring that value is set, - // even on a cache hit in which getURL otherwise wouldn't be called. - // Some of the adapters that rely on this behavior are user-defined, hence compatibility hack - this.getURL(state, chain, fields); - - const cache_pos_chr = state.chr; - const {_cache_pos_start, _cache_pos_end} = this; - if (_cache_pos_start && state.start >= _cache_pos_start && _cache_pos_end && state.end <= _cache_pos_end ) { - return `${cache_pos_chr}_${_cache_pos_start}_${_cache_pos_end}`; - } else { - return `${state.chr}_${state.start}_${state.end}`; - } - } - - /** - * Stub: build the URL for any requests made by this source. - * @protected - */ - getURL(state, chain, fields) { - return this.url; - } - - /** - * Perform a network request to fetch data for this source. This is usually the method that is used to override - * when defining how to retrieve data. - * @protected - * @param {Object} state The state of the parent plot - * @param chain - * @param fields - * @returns {Promise} - */ - fetchRequest(state, chain, fields) { - const url = this.getURL(state, chain, fields); - return fetch(url).then((response) => { - if (!response.ok) { - throw new Error(response.statusText); - } - return response.text(); - }); - } - - /** - * Gets the data for just this source, typically via a network request (but using cache where possible) - * - * For most use cases, it is better to override `fetchRequest` instead, to avoid bypassing the cache mechanism - * by accident. - * @protected - * @return {Promise} - */ - getRequest(state, chain, fields) { - let req; - const cacheKey = this.getCacheKey(state, chain, fields); - - if (this._enableCache && typeof(cacheKey) !== 'undefined' && cacheKey === this._cachedKey) { - req = Promise.resolve(this._cachedResponse); // Resolve to the value of the current promise - } else { - req = this.fetchRequest(state, chain, fields); - if (this._enableCache) { - this._cachedKey = cacheKey; - this._cache_pos_start = state.start; - this._cache_pos_end = state.end; - this._cachedResponse = req; - } - } - return req; - } - - /** - * Ensure the server response is in a canonical form, an array of one object per record. [ {field: oneval} ]. - * If the server response contains columns, reformats the response from {column1: [], column2: []} to the above. - * - * Does not apply namespacing, transformations, or field extraction. - * - * May be overridden by data adapters that inherently return more complex payloads, or that exist to annotate other - * sources (eg, if the payload provides extra data rather than a series of records). - * @protected - * @param {Object[]|Object} data The original parsed server response - */ - normalizeResponse(data) { - if (Array.isArray(data)) { - // Already in the desired form - return data; - } - // Otherwise, assume the server response is an object representing columns of data. - // Each array should have the same length (verify), and a given array index corresponds to a single row. - const keys = Object.keys(data); - const N = data[keys[0]].length; - const sameLength = keys.every(function (key) { - const item = data[key]; - return item.length === N; - }); - if (!sameLength) { - throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`); - } - - // Go down the rows, and create an object for each record - const records = []; - const fields = Object.keys(data); - for (let i = 0; i < N; i++) { - const record = {}; - for (let j = 0; j < fields.length; j++) { - record[fields[j]] = data[fields[j]][i]; - } - records.push(record); - } - return records; - } - - /** - * Hook to post-process the data returned by this source with new, additional behavior. - * (eg cleaning up API values or performing complex calculations on the returned data) - * - * @protected - * @param {Object[]} records The parsed data from the source (eg standardized api response) - * @param {Object} chain The data chain object. For example, chain.headers may provide useful annotation metadata - * @returns {Object[]|Promise} The modified set of records - */ - annotateData(records, chain) { - // Default behavior: no transformations - return records; - } - - /** - * Clean up the server records for use by datalayers: extract only certain fields, with the specified names. - * Apply per-field transformations as appropriate. - * - * This hook can be overridden, eg to create a source that always returns all records and ignores the "fields" array. - * This is particularly common for sources at the end of a chain- many "dependent" sources do not allow - * cherry-picking individual fields, in which case by **convention** the fields array specifies "last_source_name:all" - * - * @protected - * @param {Object[]} data One record object per element - * @param {String[]} fields The names of fields to extract (as named in the source data). Eg "afield" - * @param {String[]} outnames How to represent the source fields in the output. Eg "namespace:afield|atransform" - * @param {function[]} trans An array of transformation functions (if any). One function per data element, or null. - * @protected - */ - extractFields (data, fields, outnames, trans) { - //intended for an array of objects - // [ {"id":1, "val":5}, {"id":2, "val":10}] - // Since a number of sources exist that do not obey this format, we will provide a convenient pass-through - if (!Array.isArray(data)) { - return data; - } - - if (!data.length) { - // Sometimes there are regions that just don't have data- this should not trigger a missing field error message! - return data; - } - - const fieldFound = []; - for (let k = 0; k < fields.length; k++) { - fieldFound[k] = 0; - } - - const records = data.map(function (item) { - const output_record = {}; - for (let j = 0; j < fields.length; j++) { - let val = item[fields[j]]; - if (typeof val != 'undefined') { - fieldFound[j] = 1; - } - if (trans && trans[j]) { - val = trans[j](val); - } - output_record[outnames[j]] = val; - } - return output_record; - }); - fieldFound.forEach(function(v, i) { - if (!v) { - throw new Error(`field ${fields[i]} not found in response for ${outnames[i]}`); - } - }); - return records; - } - - /** - * Combine records from this source with others in the chain to yield final chain body. - * Handles merging this data with other sources (if applicable). - * - * @protected - * @param {Object[]} data The data That would be returned from this source alone - * @param {Object} chain The data chain built up during previous requests - * @param {String[]} fields - * @param {String[]} outnames - * @param {String[]} trans - * @return {Promise|Object[]} The new chain body - */ - combineChainBody(data, chain, fields, outnames, trans) { - return data; - } - - /** - * Coordinates the work of parsing a response and returning records. This is broken into 4 steps, which may be - * overridden separately for fine-grained control. Each step can return either raw data or a promise. - * - * @see {module:LocusZoom_Adapters~BaseAdapter#normalizeResponse} - * @see {module:LocusZoom_Adapters~BaseAdapter#annotateData} - * @see {module:LocusZoom_Adapters~BaseAdapter#extractFields} - * @see {module:LocusZoom_Adapters~BaseAdapter#combineChainBody} - * @protected - * - * @param {String|Object} resp The raw data associated with the response - * @param {Object} chain The combined parsed response data from this and all other requests made in the chain - * @param {String[]} fields Array of requested field names (as they would appear in the response payload) - * @param {String[]} outnames Array of field names as they will be represented in the data returned by this source, - * including the namespace. This must be an array with the same length as `fields` - * @param {Function[]} trans The collection of transformation functions to be run on selected fields. - * This must be an array with the same length as `fields` - * @returns {Promise} A promise that resolves to an object containing - * request metadata (`headers: {}`), the consolidated data for plotting (`body: []`), and the individual responses that would be - * returned by each source in the chain in isolation (`discrete: {}`) - */ - parseResponse (resp, chain, fields, outnames, trans) { - const source_id = this.source_id || this.constructor.name; - if (!chain.discrete) { - chain.discrete = {}; - } - - const json = typeof resp == 'string' ? JSON.parse(resp) : resp; - - // Perform the 4 steps of parsing the payload and return a combined chain object - return Promise.resolve(this.normalizeResponse(json.data || json)) - .then((standardized) => { - // Perform calculations on the data from just this source - return Promise.resolve(this.annotateData(standardized, chain)); - }).then((data) => { - return Promise.resolve(this.extractFields(data, fields, outnames, trans)); - }).then((one_source_body) => { - // Store a copy of the data that would be returned by parsing this source in isolation (and taking the - // fields array into account). This is useful when we want to re-use the source output in many ways. - chain.discrete[source_id] = one_source_body; - return Promise.resolve(this.combineChainBody(one_source_body, chain, fields, outnames, trans)); - }).then((new_body) => { - return { header: chain.header || {}, discrete: chain.discrete, body: new_body }; - }); - } - - /** - * Fetch the data from the specified data adapter, and apply transformations requested by an external consumer. - * This is the public-facing datasource method that will most be called by the plot, but custom data adapters will - * almost never want to override this method directly- more specific hooks are provided to control individual pieces - * of the request lifecycle. - * - * @private - * @param {Object} state The current "state" of the plot, such as chromosome and start/end positions - * @param {String[]} fields Array of field names that the plot has requested from this data adapter. (without the "namespace" prefix) - * @param {String[]} outnames Array describing how the output data should refer to this field. This represents the - * originally requested field name, including the namespace. This must be an array with the same length as `fields` - * @param {Function[]} trans The collection of transformation functions to be run on selected fields. - * This must be an array with the same length as `fields` - * @returns {function} A callable operation that can be used as part of the data chain - */ - getData(state, fields, outnames, trans) { - if (this.preGetData) { // TODO try to remove this method if at all possible - const pre = this.preGetData(state, fields, outnames, trans); - if (this.pre) { - state = pre.state || state; - fields = pre.fields || fields; - outnames = pre.outnames || outnames; - trans = pre.trans || trans; - } - } - - return (chain) => { - if (this.__dependentSource && chain && chain.body && !chain.body.length) { - // A "dependent" source should not attempt to fire a request if there is no data for it to act on. - // Therefore, it should simply return the previous data chain. - return Promise.resolve(chain); - } - - return this.getRequest(state, chain, fields).then((resp) => { - return this.parseResponse(resp, chain, fields, outnames, trans); - }); - }; + constructor() { + throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.'); } } @@ -688,20 +347,7 @@ class BaseAdapter { * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request. * @inheritDoc */ -class BaseApiAdapter extends BaseAdapter { - parseInit(config) { - super.parseInit(config); - - /** - * @private - * @member {String} - */ - this.url = config.url; - if (!this.url) { - throw new Error('Source not initialized with required URL'); - } - } -} +class BaseApiAdapter extends BaseAdapter {} /** @@ -715,7 +361,7 @@ class BaseApiAdapter extends BaseAdapter { * field in association data by looking for the field name `position` or `pos`. * * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter */ class GwasCatalogLZ extends BaseUMAdapter { /** @@ -742,7 +388,9 @@ class GwasCatalogLZ extends BaseUMAdapter { // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build). // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date const source_query = build ? `&build=${build}` : ` and id eq ${source}`; - return `${this._url }?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`; + + const base = super._getURL(request_options); + return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`; } } @@ -778,7 +426,9 @@ class GeneLZ extends BaseUMAdapter { // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build). // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date const source_query = build ? `&build=${build}` : ` and source in ${source}`; - return `${this._url}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`; + + const base = super._getURL(request_options); + return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`; } } @@ -828,14 +478,6 @@ class GeneConstraintLZ extends BaseLZAdapter { return Object.assign({}, options); } - /** - * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps. - */ - _normalizeResponse(response_text) { - const data = JSON.parse(response_text); - return data.data; - } - _performRequest(options) { let {query, build} = options; if (!query.length || query.length > 25 || build === 'GRCh38') { @@ -862,6 +504,14 @@ class GeneConstraintLZ extends BaseLZAdapter { return response.text(); }).catch((err) => []); } + + /** + * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps. + */ + _normalizeResponse(response_text) { + const data = JSON.parse(response_text); + return data.data; + } } /** @@ -887,7 +537,9 @@ class RecombLZ extends BaseUMAdapter { // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build). // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date const source_query = build ? `&build=${build}` : ` and id in ${source}`; - return `${this._url}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`; + + const base = super._getURL(request_options); + return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`; } } @@ -933,8 +585,9 @@ class PheWASLZ extends BaseUMAdapter { if (!build || !Array.isArray(build) || !build.length) { throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' ')); } + const base = super._getURL(request_options); const url = [ - this._url, + base, "?filter=variant eq '", encodeURIComponent(request_options.variant), "'&format=objects&", build.map(function (item) { return `build=${encodeURIComponent(item)}`; @@ -949,8 +602,13 @@ class PheWASLZ extends BaseUMAdapter { } } -export { BaseAdapter, BaseApiAdapter, BaseLZAdapter, BaseUMAdapter }; +// Deprecated symbols +export { BaseAdapter, BaseApiAdapter }; + +// Usually used as a parent class for custom code +export { BaseLZAdapter, BaseUMAdapter }; +// Usually used as a standalone class export { AssociationLZ, GeneConstraintLZ, diff --git a/esm/ext/lz-credible-sets.js b/esm/ext/lz-credible-sets.js index d8e32e69..2a2cb560 100644 --- a/esm/ext/lz-credible-sets.js +++ b/esm/ext/lz-credible-sets.js @@ -54,7 +54,6 @@ function install (LocusZoom) { * create a credible set for the entire region; the resulting set may include things below the line of significance. */ constructor(config) { - config.url = true; // FIXME: This is embarrassing super(...arguments); if (!(this._config.join_fields && this._config.join_fields.log_pvalue)) { throw new Error(`Source config for ${this.constructor.name} must specify how to find 'fields.log_pvalue'`); @@ -76,13 +75,11 @@ function install (LocusZoom) { _buildRequestOptions(options, assoc_data) { const base = super._buildRequestOptions(...arguments); base._assoc_data = assoc_data; - console.log('hi'); return base; } _performRequest(options) { const {_assoc_data} = options; - console.log('calculating'); if (!_assoc_data.length) { // No credible set can be calculated because there is no association data for this region return Promise.resolve([]); @@ -121,7 +118,7 @@ function install (LocusZoom) { // If the calculation cannot be completed, return the data without annotation fields console.error(e); } - return Promise.resolve(JSON.stringify(_assoc_data)); + return Promise.resolve(_assoc_data); } } diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index 893e3969..84d6dda3 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -59,7 +59,9 @@ function install (LocusZoom) { _getURL(request_options) { const source = this._config.source; const query = `?filter=id in ${source} and chromosome eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}`; - return `${this._url}${query}`; + + const base = super._getURL(request_options); + return `${base}${query}`; } } diff --git a/esm/ext/lz-tabix-source.js b/esm/ext/lz-tabix-source.js index c7daad47..33779db3 100644 --- a/esm/ext/lz-tabix-source.js +++ b/esm/ext/lz-tabix-source.js @@ -63,7 +63,6 @@ function install(LocusZoom) { */ class TabixUrlSource extends BaseLZAdapter { constructor(config) { - config.url = config.url_data; super(config); if (!config.parser_func || !config.url_data) { throw new Error('Tabix source is missing required configuration options'); From 0914d2e3ccc00294a479a6c8f926eabc79387d74 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 7 Sep 2021 00:30:00 -0400 Subject: [PATCH 008/100] Re-implement unit tests for new adapters + joins --- esm/data/adapters.js | 336 +++++++------- test/unit/data/test_adapters.js | 781 +++++++------------------------- 2 files changed, 341 insertions(+), 776 deletions(-) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 432c32da..5651b811 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -38,6 +38,27 @@ import {BaseUrlAdapter} from 'undercomplicate'; const REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/; +// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs. +// Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal +// methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the +// private API methods exist in the base class. + +class BaseAdapter { + constructor() { + throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.'); + } +} + +/** + * Base class for LocusZoom data adapters that receive their data over the web. Adds default config parameters + * (and potentially other behavior) that are relevant to URL-based requests. + * @extends module:LocusZoom_Adapters~BaseAdapter + * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request. + * @inheritDoc + */ +class BaseApiAdapter extends BaseAdapter {} + + class BaseLZAdapter extends BaseUrlAdapter { constructor(config) { super(config); @@ -48,8 +69,9 @@ class BaseLZAdapter extends BaseUrlAdapter { // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear // in the response. (gene_name instead of genes.gene_name) - const {prefix_namespace} = config; + const {prefix_namespace, limit_fields} = config; this._prefix_namespace = (typeof prefix_namespace === 'boolean') ? prefix_namespace : true; + this._limit_fields = limit_fields; } _getCacheKey(options) { @@ -76,7 +98,7 @@ class BaseLZAdapter extends BaseUrlAdapter { * @private */ _postProcessResponse(records, options) { - if (!this._prefix_namespace) { + if (!this._prefix_namespace || !Array.isArray(records)) { return records; } @@ -85,7 +107,9 @@ class BaseLZAdapter extends BaseUrlAdapter { return records.map((row) => { return Object.entries(row).reduce( (acc, [label, value]) => { - acc[`${options._provider_name}.${label}`] = value; + if (!this._limit_fields || this._fields_contract.has(label)) { + acc[`${options._provider_name}.${label}`] = value; + } return acc; }, {} @@ -172,7 +196,7 @@ class BaseUMAdapter extends BaseLZAdapter { class AssociationLZ extends BaseUMAdapter { constructor(config) { - // Minimum adapter contract hard-codes fields contract based on UM PortalDev API + // Minimum adapter contract hard-codes fields contract based on UM PortalDev API + default assoc plot layout // For layers that require more functionality, pass extra_fields to source options config.fields = ['variant', 'position', 'log_pvalue', 'ref_allele']; @@ -193,163 +217,6 @@ class AssociationLZ extends BaseUMAdapter { } -class LDServer extends BaseUMAdapter { - constructor(config) { - // item1 = refvar, item2 = othervar - config.fields = ['chromosome2', 'position2', 'variant2', 'correlation']; - super(config); - } - - _getURL(request_options) { - const method = this._config.method || 'rsquare'; - const { - chr, start, end, - ld_refvar, - genome_build, ld_source, ld_population, - } = request_options; - - const base = super._getURL(request_options); - - return [ - base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants', - '?correlation=', method, - '&variant=', encodeURIComponent(ld_refvar), - '&chrom=', encodeURIComponent(chr), - '&start=', encodeURIComponent(start), - '&stop=', encodeURIComponent(end), - ].join(''); - } - - _buildRequestOptions(state, assoc_data) { - if (!assoc_data) { - throw new Error('LD request must depend on association data'); - } - - // If no state refvar is provided, find the most significant variant in any provided assoc data. - // Assumes that assoc satisfies the "assoc" fields contract, eg has fields variant and log_pvalue - const base = super._buildRequestOptions(...arguments); - if (!assoc_data.length) { - base._skip_request = true; - return base; - } - - const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant'); - const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue'); - - // Determine the reference variant (via user selected OR automatic-per-track) - let refvar; - let best_hit = {}; - if (state.ldrefvar) { - // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data - refvar = state.ldrefvar; - best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {}; - } else { - // find highest log-value and associated var spec - let best_logp = 0; - for (let item of assoc_data) { - const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item; - if (log_pvalue > best_logp) { - best_logp = log_pvalue; - refvar = variant; - best_hit = item; - } - } - } - - // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting. - // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase. - best_hit.lz_is_ld_refvar = true; - - // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server, - // the variant fields must be normalized to a specific format. All later LD operations will use that format. - const match = refvar && refvar.match(REGEX_MARKER); - if (!match) { - throw new Error('Could not request LD for a missing or incomplete marker format'); - } - - const [_, chrom, pos, ref, alt] = match; - // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing - // a partial match at most leaves room for potential future features. - refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip? - if (ref && alt) { - refvar += `_${ref}/${alt}`; - } - - base.ld_refvar = refvar; - - // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config - const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted. - let ld_source = state.ld_source || this._config.source || '1000G'; - const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL - - if (ld_source === '1000G' && genome_build === 'GRCh38') { - // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default. - ld_source = '1000G-FRZ09'; - } - - this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option - return Object.assign({}, base, { genome_build, ld_source, ld_population }); - } - - _getCacheKey(options) { - // LD is keyed by more than just region; append other parameters to the base cache key - const base = super._getCacheKey(options); - const { ld_refvar, ld_source, ld_population } = options; - return `${base}_${ld_refvar}_${ld_source}_${ld_population}`; - } - - _performRequest(options) { - // Skip request if this one depends on other data, in a region with no data - if (options._skip_request) { - return Promise.resolve([]); - } - // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected - const url = this._getURL(options); - - let combined = { data: {} }; - let chainRequests = function (url) { - return fetch(url).then().then((response) => { - if (!response.ok) { - throw new Error(response.statusText); - } - return response.text(); - }).then(function(payload) { - payload = JSON.parse(payload); - Object.keys(payload.data).forEach(function (key) { - combined.data[key] = (combined.data[key] || []).concat(payload.data[key]); - }); - if (payload.next) { - return chainRequests(payload.next); - } - return combined; - }); - }; - return chainRequests(url); - } -} - - -// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs. -// Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal -// methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the -// private API methods exist in the base class. - -class BaseAdapter { - constructor() { - throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.'); - } -} - -/** - * Base class for LocusZoom data adapters that receive their data over the web. Adds default config parameters - * (and potentially other behavior) that are relevant to URL-based requests. - * @extends module:LocusZoom_Adapters~BaseAdapter - * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request. - * @inheritDoc - */ -class BaseApiAdapter extends BaseAdapter {} - - /** * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data. * There can be more than one claim per variant; this adapter is written to support a visualization in which each @@ -394,6 +261,7 @@ class GwasCatalogLZ extends BaseUMAdapter { } } + /** * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format) * @public @@ -432,6 +300,7 @@ class GeneLZ extends BaseUMAdapter { } } + /** * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint) * @@ -514,6 +383,149 @@ class GeneConstraintLZ extends BaseLZAdapter { } } + +class LDServer extends BaseUMAdapter { + constructor(config) { + // item1 = refvar, item2 = othervar + config.fields = ['chromosome2', 'position2', 'variant2', 'correlation']; + super(config); + } + + __find_ld_refvar(state, assoc_data) { + const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant'); + const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue'); + + // Determine the reference variant (via user selected OR automatic-per-track) + let refvar; + let best_hit = {}; + if (state.ldrefvar) { + // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data + refvar = state.ldrefvar; + best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {}; + } else { + // find highest log-value and associated var spec + let best_logp = 0; + for (let item of assoc_data) { + const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item; + if (log_pvalue > best_logp) { + best_logp = log_pvalue; + refvar = variant; + best_hit = item; + } + } + } + + // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting. + // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase. + best_hit.lz_is_ld_refvar = true; + + // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server, + // the variant fields must be normalized to a specific format. All later LD operations will use that format. + const match = refvar && refvar.match(REGEX_MARKER); + if (!match) { + throw new Error('Could not request LD for a missing or incomplete marker format'); + } + + const [_, chrom, pos, ref, alt] = match; + // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing + // a partial match at most leaves room for potential future features. + refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip? + if (ref && alt) { + refvar += `_${ref}/${alt}`; + } + + // Return the reference variant, in a normalized format suitable for LDServer queries + return refvar; + } + + _buildRequestOptions(state, assoc_data) { + if (!assoc_data) { + throw new Error('LD request must depend on association data'); + } + + // If no state refvar is provided, find the most significant variant in any provided assoc data. + // Assumes that assoc satisfies the "assoc" fields contract, eg has fields variant and log_pvalue + const base = super._buildRequestOptions(...arguments); + if (!assoc_data.length) { + base._skip_request = true; + return base; + } + + base.ld_refvar = this.__find_ld_refvar(state, assoc_data); + + // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config + const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted. + let ld_source = state.ld_source || this._config.source || '1000G'; + const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL + + if (ld_source === '1000G' && genome_build === 'GRCh38') { + // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default. + ld_source = '1000G-FRZ09'; + } + + this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option + return Object.assign({}, base, { genome_build, ld_source, ld_population }); + } + + _getURL(request_options) { + const method = this._config.method || 'rsquare'; + const { + chr, start, end, + ld_refvar, + genome_build, ld_source, ld_population, + } = request_options; + + const base = super._getURL(request_options); + + return [ + base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants', + '?correlation=', method, + '&variant=', encodeURIComponent(ld_refvar), + '&chrom=', encodeURIComponent(chr), + '&start=', encodeURIComponent(start), + '&stop=', encodeURIComponent(end), + ].join(''); + } + + _getCacheKey(options) { + // LD is keyed by more than just region; append other parameters to the base cache key + const base = super._getCacheKey(options); + const { ld_refvar, ld_source, ld_population } = options; + return `${base}_${ld_refvar}_${ld_source}_${ld_population}`; + } + + _performRequest(options) { + // Skip request if this one depends on other data, in a region with no data + if (options._skip_request) { + return Promise.resolve([]); + } + + const url = this._getURL(options); + + // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected + let combined = { data: {} }; + let chainRequests = function (url) { + return fetch(url).then().then((response) => { + if (!response.ok) { + throw new Error(response.statusText); + } + return response.text(); + }).then(function(payload) { + payload = JSON.parse(payload); + Object.keys(payload.data).forEach(function (key) { + combined.data[key] = (combined.data[key] || []).concat(payload.data[key]); + }); + if (payload.next) { + return chainRequests(payload.next); + } + return combined; + }); + }; + return chainRequests(url); + } +} + + /** * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible) * @public @@ -543,6 +555,7 @@ class RecombLZ extends BaseUMAdapter { } } + /** * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required * for some sources (eg it does not know how to join together LD and association data). @@ -570,6 +583,7 @@ class StaticSource extends BaseLZAdapter { } } + /** * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API * @public diff --git a/test/unit/data/test_adapters.js b/test/unit/data/test_adapters.js index e3bf2231..e177dd30 100644 --- a/test/unit/data/test_adapters.js +++ b/test/unit/data/test_adapters.js @@ -1,10 +1,15 @@ +/** + LocusZoom.js Data Test Suite + Test LocusZoom Data retrieval functionality + */ + + import { assert } from 'chai'; import sinon from 'sinon'; import { AssociationLZ, - BaseAdapter, - ConnectorSource, + BaseAdapter, BaseLZAdapter, BaseUMAdapter, GeneLZ, GwasCatalogLZ, LDServer, @@ -12,348 +17,87 @@ import { StaticSource, } from '../../../esm/data/adapters'; -/** - LocusZoom.js Data Test Suite - Test LocusZoom Data access objects - */ -describe('Data adapters', function () { - describe('Base Source Behavior', function () { - describe('Source.getData', function () { - it('dependentSource skips making a request if previous sources did not add data to chain.body', function () { - const source = new BaseAdapter({}); - source.__dependentSource = true; - let requestStub = sinon.stub(source, 'getRequest'); - - const callable = source.getData(); - const noRecordsChain = { body: [] }; - callable(noRecordsChain); - - assert.ok(requestStub.notCalled, 'Request should be skipped'); - }); - - it('dependentSource makes a request if chain.body has data from previous sources', function (done) { - const source = new BaseAdapter({}); - source.__dependentSource = false; - const requestStub = sinon.stub(source, 'getRequest').callsFake(function () { - return Promise.resolve(); - }); - sinon.stub(source, 'parseResponse').callsFake(function () { - // Because this is an async test, `done` will serve as proof that parseResponse was called - done(); - }); - - const callable = source.getData(); - const hasRecordsChain = { body: [{ some: 'data' }] }; - callable(hasRecordsChain); - - assert.ok(requestStub.called, 'Request was made'); - }); - afterEach(function () { - sinon.restore(); - }); +describe('Data adapters', function () { + describe('BaseLZAdapter', function () { + + class BaseTestClass extends BaseLZAdapter { + _performRequest(options) { + // For fixture purposes, just return some data passed in. Can use this to forcibly test cache hit/miss.. + return options._test_data; + } + } + + it('checks if request can be satisfied using cached data', function () { + const source = new BaseTestClass({ prefix_namespace: false }); + const first_test_data = [{a: 1, b:2}]; + const different_if_cache_hit = [{a: 1, b:2}]; + return source.getData({ chr: '1', start: 100, end: 200, _test_data: first_test_data }) + .then((result) => { + assert.deepEqual(result, first_test_data, 'First request received data passed in'); + return source.getData({ chr: '1', start: 125, end: 175, _test_data: different_if_cache_hit }); + }) + .then((result) => { + assert.deepEqual(result, first_test_data, 'Second request receives cache data for a region that is superset'); + return source.getData({ chr: '1', start: 120, end: 180, _test_data: different_if_cache_hit }); + }).then((result) => { + assert.deepEqual(result, first_test_data, 'A third request shows that the cache is still based on the widest possible region (first request)'); + return source.getData({ chr: '2', start: 120, end: 180, _test_data: different_if_cache_hit }); + }).then((result) => assert.deepEqual(result, different_if_cache_hit, 'Diff region, so not a cache hit')); }); - describe('Source.getRequest', function () { - it('uses cached data when zooming in', function () { - const source = new BaseAdapter({}); - - const fetchStub = sinon.stub(source, 'fetchRequest').returns(Promise.resolve()); - - let state = { chr: 1, start: 500, end: 1500 }; - let chain = {}; - let fields = []; - const first_key = source.getCacheKey({ chr: 1, start: 500, end: 1500 }); - - // The flags that control cache are set in getRequest (eg, cache miss --> nework request --> update flags) - // Thus the testing plan involves calling getRequest, then checking the expected cache key based on those updated parameters - return source.getRequest(state, chain, fields) - .then(() => { - // Cache hits from the same key - assert.equal( - source.getCacheKey({ chr: 1, start: 750, end: 1250 }), - first_key, - 'Zooming in triggers a cache hit' - ); - assert.equal( - source.getCacheKey({ chr: 1, start: 625, end: 1125 }), - first_key, - 'Panning left inside cached area triggers a cache hit' - ); - assert.equal( - source.getCacheKey({ chr: 1, start: 750, end: 1250 }), - first_key, - 'Panning right inside cached area triggers a cache hit' - ); - - // Prepare for the second request, a cache miss - state = { chr: 1, start: 250, end: 1250 }; - return source.getRequest(state); - }).then(() => { - const second_key = source.getCacheKey(state); - assert.notEqual( - second_key, - first_key, - 'Panning left outside the original zoom area triggers a cache miss' - ); - assert.equal( - second_key, - source.getCacheKey({ chr: 1, start: 400, end: 900 }), - 'After a cache miss, cache hits are relative to the newly fetched data' - ); - - // Slightly weak in that most of the asserts don't actually use getRequest, but... - assert.ok(fetchStub.calledTwice, 'Two fetches triggered by cache miss'); - - assert.notEqual( - source.getCacheKey({ chr: 2, start: 250, end: 1250 }), - second_key, - 'A change in chromosome ALWAYS triggers a cache miss, even if position is the same' - ); - }); - }); - - afterEach(function () { - sinon.restore(); - }); + it('prefixes responses with the name of the provider', function () { + const source = new BaseTestClass({}); + return source.getData({ + _provider_name: 'sometest', + _test_data: [{ a: 1, b: 2 }]} + ).then((result) => assert.deepEqual(result, [{'sometest.a': 1, 'sometest.b': 2}])); }); - describe('Source.parseResponse', function () { - // Parse response is a wrapper for a set of helper methods. Test them individually, and combined. - afterEach(function () { - sinon.restore(); - }); - - describe('Source.normalizeResponse', function () { - it('should create one object per piece of data', function () { - const source = new BaseAdapter({}); - const res = source.normalizeResponse( - { a: [1, 2], b: [3, 4] }); - assert.deepEqual( - res, - [{ a: 1, b: 3 }, { a: 2, b: 4 }], - 'Correct number and union of elements' - ); - }); - - it('should require all columns of data to be of same length', function () { - const source = new BaseAdapter({}); - assert.throws( - function () { - source.normalizeResponse({ a: [1], b: [1, 2], c: [1, 2, 3] }); - }, - /expects a response in which all arrays of data are the same length/ - ); - }); - - it('should return the data unchanged if it is already in the desired shape', function () { - const source = new BaseAdapter({}); - const data = [{ a: 1, b: 3 }, { a: 2, b: 4 }]; - const res = source.normalizeResponse(data); - assert.deepEqual(res, data); - }); - }); + it('can restrict the list of returned fields to those in the fields contract', function () { + const source = new BaseTestClass({ limit_fields: true, fields: ['b'] }); + return source.getData({ + _provider_name: 'sometest', + _test_data: [{ a: 1, b: 2 }]} + ).then((result) => assert.deepEqual(result, [{ 'sometest.b': 2}])); + }); - describe('Source.annotateData', function () { - it('should be able to add fields to the returned records', function () { - const custom_class = class extends BaseAdapter { - annotateData(records) { - // Custom hook that adds a field to every parsed record - return records.map(function (item) { - item.force = true; - return item; - }); - } - }; - - // Async test depends on promise - return new custom_class({}).parseResponse([{ a: 1, b: 1 }, { - a: 2, - b: 2, - }], {}, ['a', 'b', 'force'], ['a', 'b', 'force'], []).then(function (records) { - records.body.forEach(function (item) { - assert.ok(item.force, 'Record should have an additional key not in raw server payload'); - }); - }); - }); - - it('should be able to annotate based on info in the body and chain', function () { - const custom_class = class extends BaseAdapter { - annotateData(records, chain) { - return records + chain.header.param; - } - }; - const result = new custom_class({}).annotateData( - 'some data', - { header: { param: ' up the chain' } } - ); - assert.equal(result, 'some data up the chain'); - }); - }); + it('has a helper to locate dependent fields that were already namespaced', function () { + const source = new BaseTestClass({}); + const match = source._findPrefixedKey({'sometest.aaa': 1, 'sometest.a': 2, 'sometest.aa': 3}, 'a'); - describe('Source.extractFields', function () { - it('extracts the specified fields from each record', function () { - const source = new BaseAdapter({}); - const res = source.extractFields( - [{ 'id': 1, 'val': 5 }, { 'id': 2, 'val': 10 }], - ['id'], ['namespace:id'], [null] - ); - assert.deepEqual(res, [{ 'namespace:id': 1 }, { 'namespace:id': 2 }]); - }); - - it('applies value transformations where appropriate', function () { - const source = new BaseAdapter({}); - const res = source.extractFields( - [{ 'id': 1, 'val': 5 }, { 'id': 2, 'val': 10 }], - ['id', 'val'], ['namespace:id|add1', 'bork:bork'], [function (val) { - return val + 1; - }, null] - ); - // Output fields can be mapped to any arbitrary based on the field->outnames provided - assert.deepEqual(res, [{ 'namespace:id|add1': 2, 'bork:bork': 5 }, { - 'namespace:id|add1': 3, - 'bork:bork': 10, - }]); - }); - - it('throws an error when requesting a field not present in at least one record', function () { - const source = new BaseAdapter({}); - - assert.throws(function () { - source.extractFields( - [{ 'a': 1 }, { 'a': 2 }], - ['b'], ['namespace:b'], [null] - ); - }, /field b not found in response for namespace:b/); - }); - }); + assert.equal(match, 'sometest.a', 'Found correct key and ignored partial suffixes'); - describe('Source.combineChainBody', function () { - it('returns only the body by default', function () { - const source = new BaseAdapter({}); - - const expectedBody = [{ 'namespace:a': 1 }]; - const res = source.combineChainBody(expectedBody, { header: {}, body: [], discrete: {} }); - - assert.deepEqual(res, expectedBody); - }); - - it('can build a body based on records from all sources in the chain', function () { - const custom_class = class extends BaseAdapter { - combineChainBody(records, chain) { - return records.map(function (item, index) { - return Object.assign({}, item, chain.body[index]); - }); - } - }; - const source = new custom_class({}); - - var records = [{ 'namespace:a': 1 }]; - const res = source.combineChainBody(records, { - header: {}, - body: [{ 'namespace:b': 2 }], - discrete: {}, - }); - - assert.deepEqual(res, [{ 'namespace:a': 1, 'namespace:b': 2 }]); - }); - }); - - describe('integration of steps', function () { - it('should interpret a string response as JSON', function () { - const source = new BaseAdapter({}); - - return source.parseResponse('{"a_field": ["val"]}', {}, ['a_field'], ['namespace:a_field'], [null]) - .then(function (chain) { - assert.deepEqual(chain.body, [{ 'namespace:a_field': 'val' }]); - }); - }); - - it('should store annotations in body and chain.discrete where appropriate', function () { - const custom_class = class CustomClass extends BaseAdapter { - annotateData(data) { - return `${data} with annotation`; - } - - normalizeResponse(data) { - return data; - } - - extractFields(data) { - return data; - } - }; - - const result = new custom_class({}).parseResponse({ data: 'a response' }, {}); - return result.then(function (chain) { - assert.deepEqual(chain.discrete, { CustomClass: 'a response with annotation' }, 'Discrete response uses annotations'); - assert.deepEqual(chain.body, 'a response with annotation', 'Combined body uses annotations'); - }); - }); - - it('integrates all methods via promise semantics', function () { - // Returning a promise is optional, but should be supported if a custom subclass chooses to do so - const custom_class = class CustomClass extends BaseAdapter { - normalizeResponse() { - return Promise.resolve([{ a: 1 }]); - } - - annotateData(records) { - return Promise.resolve(records.map(function (item) { - item.b = item.a + 1; - return item; - })); - } - - extractFields(data, fields, outnames, trans) { - const rec = data.map(function (item) { - return { 'bfield': item.b }; - }); - return Promise.resolve(rec); - } - - combineChainBody(records) { - return Promise.resolve(records); - } - }; - - const result = new custom_class({}).parseResponse({}, {}); - const thisBody = [{ bfield: 2 }]; - const expected = { header: {}, discrete: { CustomClass: thisBody }, body: thisBody }; - return result.then(function (final) { - assert.deepEqual(final.body, expected.body, 'Chain produces expected result body'); - assert.deepEqual(final.discrete, expected.discrete, 'The parsed results from this source are also stored in chain.discrete'); - }); - }); - }); + assert.throws( + () => source._findPrefixedKey([{'sometest.aaa': 1 }], 'no_such_key'), + /Could not locate/, + 'Pedantically insists that data match the expected contract' + ); }); }); - describe('Association Data Source', function () { - it('allows normalization + sorting of data', function () { - const source = new AssociationLZ({ - url: 'locuszoom.org', params: { sort: true }, - }); + describe('BaseUMAdapter', function () { + it('normalizes column-based API responses to rows', function () { + const adapter = new BaseUMAdapter({ prefix_namespace: false }); + const result = adapter._normalizeResponse({a: [1], b: [2], c: [3]}); + assert.deepEqual(result, [{ a: 1, b: 2, c: 3 }]); + }); - const sampleData = { position: [2, 1] }; - const expectedData = [{ 'position': 1 }, { 'position': 2 }]; + it('does not try to normalize responses that are already an array', function () { + const adapter = new BaseUMAdapter({ prefix_namespace: false }); + const data = [{ a: 1, b: 2, c: 3 }]; + const result = adapter._normalizeResponse(data); - return source.parseResponse(sampleData, {}, ['position'], ['position'], [null]) - .then(function (resp) { - assert.deepEqual(resp.body, expectedData, 'Results are sorted by position'); - }); + result[0].d = 4; // Mutable reference only works if data is returned as is instead of being reassembled + assert.deepEqual(result, data, 'Data returned as is'); }); - it('usually returns normalized data in the order the data was provided', function () { - const source = new AssociationLZ({ - url: 'locuszoom.org', params: {}, - }); - const sampleData = { position: [2, 1] }; - const expectedData = [{ 'position': 2 }, { 'position': 1 }]; - return source.parseResponse(sampleData, {}, ['position'], ['position'], [null]) - .then(function (resp) { - assert.deepEqual(resp.body, expectedData, 'Order of results matches server response'); - }); + it('warns if trying to normalize a response with arrays of different lengths', function () { + const adapter = new BaseUMAdapter({ prefix_namespace: false }); + assert.throws( + () => adapter._normalizeResponse({ a: [1, 2], b: [3] }), + /same length/ + ); }); }); @@ -363,37 +107,120 @@ describe('Data adapters', function () { const source_name = source_type.name; it('accepts either build or source as config option, but not both', function () { - let source = new source_type({ url: 'www.fake.test', params: { source: 'a', build: 'GRCh37' } }); + let source = new source_type({ url: 'www.fake.test', source: 'a', build: 'GRCh37' }); assert.throws(function () { - source.getURL({}); + source._getURL({}); }, /must provide a parameter specifying either/, `Bad configuration should raise an exception in ${source_type}`); - source = new source_type({ url: 'www.fake.test', params: { source: 'a' } }); - let url = source.getURL({}); + source = new source_type({ url: 'www.fake.test', source: 'a' }); + let url = source._getURL({}); assert.ok(url.match(/ in a/), `Works when specifying source ID for ${source_name}`); source = new source_type({ url: 'www.fake.test' }); - url = source.getURL({ genome_build: 'GRCh37' }); + url = source._getURL({ genome_build: 'GRCh37' }); assert.ok(url.match(/GRCh37/), `Works when specifying build name for ${source_name}`); }); it('gives precedence to state.genome_build over its own config', function () { - const source = new source_type({ url: 'www.fake.test', params: { build: 'GRCh37' } }); - const url = source.getURL({ genome_build: 'GRCh38' }); + const source = new source_type({ url: 'www.fake.test', build: 'GRCh37' }); + const url = source._getURL({ genome_build: 'GRCh38' }); let pattern = /build=GRCh38/; assert.ok(url.match(pattern), `${source_name} produced formed URL: ${url}`); }); it('validates genome build', function () { - const source = new source_type({ url: 'www.fake.test', params: { build: 99 } }); + const source = new source_type({ url: 'www.fake.test', build: 99 }); assert.throws(function () { - source.getURL({}); - }, /must specify a valid genome build number/, `Should validate options for ${source_name}`); + source._getURL({}); + }, /must specify a valid 'genome_build'/, `Should validate options for ${source_name}`); }); }); }); + describe('StaticSource', function () { + it('Returns the exact data provided by the user, regardless of region', function () { + const data = { a: 1, b: 2, c: 3 }; + const source = new StaticSource({ data }); + return source.getData({ chr: 1, start: 2, end: 3}) + .then((result) => { + assert.deepEqual(data, result, 'Returns hard-coded data'); + // Deliberately mutate data, then refetch to ensure that mutations on one request don't pollute shared cache. + data.d = 4; + return source.getData({ chr: 'X', start: 500, end: 1000}); + }) + .then((result) => assert.deepEqual(data, result, 'Returns hard-coded data')); + }); + }); + + describe('LDServer', function () { + beforeEach(function () { + this._assoc_data = [ + // Deliberately use several variant formats to verify normalization + { 'assoc.variant': '1:23_A/C', 'assoc.log_pvalue': 0.2 }, + { 'assoc.variant': '1:24:A:C', 'assoc.log_pvalue': 125 }, + { 'assoc.variant': '1-25-A-C', 'assoc.log_pvalue': 72 }, + ]; + }); + + it('finds the best variant if none is provided', function () { + const provider = new LDServer({}); + const refvar = provider.__find_ld_refvar({}, this._assoc_data); + assert.equal(refvar, '1:24_A/C', 'Finds variant with max log_pvalue and normalizes spec'); + assert.equal(this._assoc_data[1].lz_is_ld_refvar, true, 'Annotates refvar'); + }); + + it('prefers a refvar from plot.state if one is provided', function () { + const provider = new LDServer({}); + const refvar = provider.__find_ld_refvar({ ldrefvar: '1-25-A-C' }, this._assoc_data); + assert.equal(refvar, '1:25_A/C', 'Uses refvar in plot.state and normalizes spec'); + assert.equal(this._assoc_data[2].lz_is_ld_refvar, true, 'Annotates refvar'); + }); + + it('skips the request if no assoc data was present', function () { + const source37 = new LDServer({ url: 'www.fake.test', source: '1000G', build: 'GRCh37' }); + + let request_options = source37._buildRequestOptions({ ldrefvar: '1:2_A/B' }, []); + assert.ok(request_options._skip_request, 'Request is flagged to skip'); + + assert.throws( + () => source37._buildRequestOptions({ ldrefvar: '1:2_A/B' }, null), + /must depend on/, + 'Warns if the adapter is totally misused (not part of a dependency chain)' + ); + + assert.throws( + () => source37._buildRequestOptions({ ldrefvar: '1:2_A/B' }, [{ some_other_data: true, meets_assoc_contract: false }]), + /required key name/, + 'Warns if the adapter is totally misused (not given association data)' + ); + + }); + + it('chooses best 1000G panel for the given build', function () { + const source37 = new LDServer({ url: 'www.fake.test', source: '1000G', build: 'GRCh37' }); + let request_options = source37._buildRequestOptions({ ldrefvar: '1:2_A/B' }, this._assoc_data); + assert.equal(request_options.genome_build, 'GRCh37', 'Build 37 detected'); + assert.equal(request_options.ld_source, '1000G', 'Build 37 uses 1000G panel (old)'); + + const source38 = new LDServer({ url: 'www.fake.test', source: '1000G', build: 'GRCh38' }); + request_options = source38._buildRequestOptions({ ldrefvar: '1:2_A/B' }, this._assoc_data); + + assert.equal(request_options.genome_build, 'GRCh38', 'Build 38 detected'); + assert.equal(request_options.ld_source, '1000G-FRZ09', 'Build 38 uses 1000G panel (upgraded)'); + }); + + it('validates the selected build name', function () { + const source = new LDServer({ url: 'www.fake.test', build: 99 }); + assert.throws(() => { + source._buildRequestOptions({}, this._assoc_data); + }, /must specify a valid 'genome_build'/); + }); + }); +}); + + +describe.skip('Data adapters', function () { describe('GwasCatalog Source', function () { beforeEach(function () { this.exampleData = [ @@ -468,281 +295,5 @@ describe('Data adapters', function () { ]); }); }); - - describe('LDServer Source', function () { - it('validates the selected build name', function () { - const source = new LDServer({ url: 'www.fake.test', params: { build: 99 } }); - assert.throws(function () { - source.getURL({}); - }, /must specify a valid genome build number/); - }); - - it('will prefer a refvar in plot.state if one is provided', function () { - const source = new LDServer({ url: 'www.fake.test', params: { build: 'GRCh37' } }); - const [ref, _] = source.getRefvar( - { ldrefvar: '12:100_A/C' }, - { header: {}, body: [{ id: 'a', pvalue: 0 }] }, - ['ldrefvar', 'state'] - ); - assert.equal(ref, '12:100_A/C'); - }); - - it('auto-selects the best reference variant (lowest pvalue)', function () { - const source = new LDServer({ url: 'www.fake.test', params: { build: 'GRCh37' } }); - const [ref, _] = source.getRefvar( - {}, - { - header: {}, - body: [ - { id: '12:100_A/A', pvalue: 0.5 }, - { id: '12:100_A/B', pvalue: 0.05 }, - { id: '12:100_A/C', pvalue: 0.1 }, - ], - }, - ['isrefvar', 'state'] - ); - assert.equal(ref, '12:100_A/B'); - }); - - it('auto-selects the best reference variant (largest nlog_pvalue)', function () { - const source = new LDServer({ url: 'www.fake.test', params: { build: 'GRCh37' } }); - const [ref, _] = source.getRefvar( - {}, - { - header: {}, - body: [ - { id: '12:100_A/A', log_pvalue: 10 }, - { id: '12:100_A/B', log_pvalue: 50 }, - { id: '12:100_A/C', log_pvalue: 7 }, - ], - }, - ['isrefvar', 'state'] - ); - assert.equal(ref, '12:100_A/B'); - }); - - it('correctly identifies the variant-marker field', function () { - // - const source_default = new LDServer({ url: 'www.fake.test', params: { build: 'GRCh37' } }); - let dataFields = source_default.findMergeFields( - { body: [{ 'assoc:id': 'a', log_pvalue: 10 }] } - ); - assert.equal(dataFields.id, 'assoc:id', 'Uses a default option (ID)'); - - dataFields = source_default.findMergeFields( - { body: [{ 'assoc:variant': 'a', log_pvalue: 10 }] } - ); - assert.equal(dataFields.id, 'assoc:variant', 'Uses a default option (variant name)'); - - const source_options = new LDServer({ - url: 'www.fake.test', - params: { build: 'GRCh37', id_field: 'marker' }, - }); - dataFields = source_options.findMergeFields( - { body: [{ 'assoc:id': 'a', 'assoc:marker': 'b', log_pvalue: 10 }] } - ); - assert.equal(dataFields.id, 'assoc:marker', 'Uses a provided option (from params.id_field)'); - - dataFields = source_options.findMergeFields( - { body: [{ 'assoc:onefish': 'a', 'assoc:twofish': 'b', log_pvalue: 10 }] } - ); - assert.equal(dataFields.id, null, 'There is no field matching the requested ID field'); - }); - - it('coerces variant formats to one expected by the LD server', function () { - const source = new LDServer({ url: 'www.fake.test', params: { build: 'GRCh37' } }); - - const portal_format = '8:117974679:G:A'; - const ldserver_format = '8:117974679_G/A'; - const request_url = source.getURL({ ldrefvar: portal_format }, { - header: {}, - body: [], - }, ['isrefvar', 'state']); - assert.equal( - request_url, - source.getURL({ ldrefvar: ldserver_format }, { header: {}, body: [] }, ['isrefvar', 'state']) - ); - assert.ok(request_url.includes(encodeURIComponent(ldserver_format))); - }); - - it('coerces variant formats, omitting ref-alt if not provided', function () { - const source = new LDServer({ url: 'www.fake.test', params: { build: 'GRCh37' } }); - const norefvar_topmed = '8-117974679'; - const ldserver_format = '8:117974679'; - const request_url = source.getURL({ ldrefvar: norefvar_topmed }, { - header: {}, - body: [], - }, ['isrefvar', 'state']); - assert.equal( - request_url, - source.getURL({ ldrefvar: ldserver_format }, { header: {}, body: [] }, ['isrefvar', 'state']) - ); - assert.ok(request_url.includes(encodeURIComponent(ldserver_format))); - }); - - it('chooses best 1000G panel for the given build', function () { - const source37 = new LDServer({ url: 'www.fake.test', params: { source: '1000G', build: 'GRCh37' } }); - let request_url = source37.getURL({ ldrefvar: '1:2_A/B' }, { - header: {}, - body: [], - }, ['isrefvar', 'state']); - assert.equal( - request_url, - 'www.fake.testgenome_builds/GRCh37/references/1000G/populations/ALL/variants?correlation=rsquare&variant=1%3A2_A%2FB&chrom=undefined&start=undefined&stop=undefined', - 'Build 37 uses 1000G panel (old)' - ); - - const source38 = new LDServer({ url: 'www.fake.test', params: { source: '1000G', build: 'GRCh38' } }); - request_url = source38.getURL({ ldrefvar: '1:2_A/B' }, { - header: {}, - body: [], - }, ['isrefvar', 'state']); - assert.equal( - request_url, - 'www.fake.testgenome_builds/GRCh38/references/1000G-FRZ09/populations/ALL/variants?correlation=rsquare&variant=1%3A2_A%2FB&chrom=undefined&start=undefined&stop=undefined', - 'Build 38 uses 1000G panel (upgraded)' - ); - }); - }); - - describe('Static Data Source', function () { - beforeEach(function () { - this.data = [ - { x: 0, y: 3, z: 8 }, - { x: 2, y: 7, h: 5 }, - { x: 8, y: 1, q: 6 }, - ]; - this.source = new StaticSource(this.data); - }); - it('should store data passed to the constructor', function () { - assert.deepEqual(this.source._data, this.data); - }); - it('should pass only specifically requested fields and respect namespacing', function () { - return this.source.getData({}, ['x'], ['test:x'], [null])({ - header: {}, - body: [], - discrete: {}, - }).then((data) => { - const expected_data = [{ 'test:x': 0 }, { 'test:x': 2 }, { 'test:x': 8 }]; - assert.hasAllKeys(data, ['header', 'body', 'discrete']); - assert.deepEqual(data.body, expected_data); - }); - }); - it('should pass only specifically requested ', function () { - return this.source.getData({}, ['q'], ['test:q'], [null])({ - header: {}, - body: [], - discrete: {}, - }).then((data) => { - const expected_data = [{ 'test:q': undefined }, { 'test:q': undefined }, { 'test:q': 6 }]; - assert.deepEqual(data.body, expected_data); - }); - }); - }); - - describe('ConnectorSource', function () { - beforeEach(function () { - // Create a source that internally looks for data as "first" from the specified - this.basic_config = { sources: { first: 'a_source', second: 'b_source' } }; - this.basic_source = class test_connector extends ConnectorSource { - combineChainBody(records, chain) { - // A sample method that uses 2 chain sources + an existing body to build an combined response - - // Tell the internal method how to find the actual data it relies on internally, regardless of how - // it is called in the namespaced data chain - const nameFirst = this._source_name_mapping['first']; - const nameSecond = this._source_name_mapping['second']; - - records.forEach(function (item) { - item.a = chain.discrete[nameFirst].a_field; - item.b = chain.discrete[nameSecond].b_field; - }); - return records; - } - - _getRequiredSources() { - return ['first', 'second']; - } - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it('must specify the data it requires from other sources', function () { - const source = class extends ConnectorSource { - _getRequiredSources() { - return []; - } - }; - assert.throws( - function () { - new source(); - }, - /Connectors must specify the data they require as config.sources = {internal_name: chain_source_id}} pairs/ - ); - assert.ok( - new source(this.basic_config), - 'Correctly specifies the namespaces containing data that this connector relies on' - ); - }); - it('must implement a combineChainBody method', function () { - const source = class extends ConnectorSource { - _getRequiredSources() { - return []; - } - }; - assert.throws( - () => { - new source(this.basic_config).combineChainBody(); - }, - /This method must be implemented in a subclass/ - ); - }); - it('should fail if the namespaces it relies on are not present in the chain', function () { - const instance = new this.basic_source(this.basic_config); - assert.throws( - function () { - instance.getRequest({}, { discrete: {} }); - }, - /test_connector cannot be used before loading required data for: a_source/ - ); - }); - it('should not make any network requests', function () { - const instance = new this.basic_source(this.basic_config); - const fetchSpy = sinon.stub(instance, 'fetchRequest'); - - return instance.getRequest({}, { discrete: { a_source: 1, b_source: 2 } }) - .then(function () { - assert.ok(fetchSpy.notCalled, 'No network request was fired'); - }); - }); - it('should not return any new data from getRequest', function () { - const instance = new this.basic_source(this.basic_config); - const expectedBody = { sample: 'response data' }; - return instance.getRequest({}, { discrete: { a_source: 1, b_source: 2 }, body: expectedBody }) - .then(function (records) { - assert.deepEqual(records, expectedBody, 'Should return the previous body'); - }); - }); - it('should build a response by combining data from multiple places', function () { - // Should have access to data in both chain.discrete and chain.body. (connectors don't have their own data) - // Not every source in chain.discrete has to be an array of records- this tests arbitrary blobs of JSON - const rawChain = { a_source: { a_field: 'aaa' }, b_source: { b_field: 'bbb' } }; - const expectedBody = [{ who: 1, a: 'aaa', b: 'bbb' }, { what: 2, a: 'aaa', b: 'bbb' }]; - - const instance = new this.basic_source(this.basic_config); - return instance.getData()( - { - discrete: rawChain, - body: [{ who: 1 }, { what: 2 }], - } - ).then(function (response) { - assert.deepEqual(response.body, expectedBody, 'Response body was correctly annotated'); - assert.deepEqual(response.discrete, rawChain, 'The chain of individual sources was not changed'); - }); - }); - }); }); From de4ce137464cc80423a919f651ccac4291b1aa3a Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 7 Sep 2021 17:42:46 -0400 Subject: [PATCH 009/100] Finish tests for adapters + joins [ci skip] --- test/unit/data/test_adapters.js | 102 ++++++-------------------------- test/unit/data/test_joins.js | 102 ++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 85 deletions(-) create mode 100644 test/unit/data/test_joins.js diff --git a/test/unit/data/test_adapters.js b/test/unit/data/test_adapters.js index e177dd30..3263ac97 100644 --- a/test/unit/data/test_adapters.js +++ b/test/unit/data/test_adapters.js @@ -2,16 +2,14 @@ LocusZoom.js Data Test Suite Test LocusZoom Data retrieval functionality */ - - import { assert } from 'chai'; -import sinon from 'sinon'; import { - AssociationLZ, - BaseAdapter, BaseLZAdapter, BaseUMAdapter, + BaseAdapter, + BaseApiAdapter, + BaseLZAdapter, + BaseUMAdapter, GeneLZ, - GwasCatalogLZ, LDServer, RecombLZ, StaticSource, @@ -19,6 +17,19 @@ import { describe('Data adapters', function () { + describe('BaseAdapter (removed)', function () { + it('warns when trying to create instance of (removed) old style adapters', function () { + assert.throws( + () => new BaseAdapter({}), + /have been replaced/ + ); + assert.throws( + () => new BaseApiAdapter({}), + /have been replaced/ + ); + }); + }); + describe('BaseLZAdapter', function () { class BaseTestClass extends BaseLZAdapter { @@ -218,82 +229,3 @@ describe('Data adapters', function () { }); }); }); - - -describe.skip('Data adapters', function () { - describe('GwasCatalog Source', function () { - beforeEach(function () { - this.exampleData = [ - { 'chrom': 1, 'pos': 3, 'log_pvalue': 1.3, 'rsid': 'rs3', 'trait': 'arithomania' }, - { 'chrom': 1, 'pos': 4, 'log_pvalue': 1.4, 'rsid': 'rs4', 'trait': 'arithomania' }, - { 'chrom': 1, 'pos': 5, 'log_pvalue': 1.5, 'rsid': 'rs5', 'trait': 'arithomania' }, - { 'chrom': 1, 'pos': 6, 'log_pvalue': 1.6, 'rsid': 'rs6', 'trait': 'arithomania' }, - ]; - this.sampleChain = { - body: [ - { 'assoc:chromosome': 1, 'assoc:position': 2 }, - { 'assoc:chromosome': 1, 'assoc:position': 4 }, - { 'assoc:chromosome': 1, 'assoc:position': 6 }, - ], - }; - }); - - it('will warn if conflicting build and source options are provided', function () { - const source = new GwasCatalogLZ({ url: 'www.fake.test', params: { source: 'fjord' } }); - assert.throws( - () => source.getURL({ genome_build: 'GRCh37' }), - /not specify both/, - 'Warns if conflicting options are used' - ); - }); - - it('aligns records based on loose position match', function () { - const source = new GwasCatalogLZ({ url: 'www.fake.test', params: { match_type: 'loose' } }); - const res = source.combineChainBody(this.exampleData, this.sampleChain, ['rsid', 'trait'], ['catalog:rsid', 'catalog:trait']); - assert.deepEqual(res, [ - { 'assoc:chromosome': 1, 'assoc:position': 2 }, // No annotations available for this point - { - 'assoc:chromosome': 1, - 'assoc:position': 4, - 'catalog:rsid': 'rs4', - 'catalog:trait': 'arithomania', - 'n_catalog_matches': 1, - }, - { - 'assoc:chromosome': 1, - 'assoc:position': 6, - 'catalog:rsid': 'rs6', - 'catalog:trait': 'arithomania', - 'n_catalog_matches': 1, - }, - ]); - }); - - it('handles the case where the same SNP has more than one catalog entry', function () { - const source = new GwasCatalogLZ({ url: 'www.fake.test' }); - const exampleData = [ - { 'chrom': 1, 'pos': 4, 'log_pvalue': 1.40, 'rsid': 'rs4', 'trait': 'arithomania' }, - { 'chrom': 1, 'pos': 4, 'log_pvalue': 1.41, 'rsid': 'rs4', 'trait': 'graphomania' }, - { 'chrom': 1, 'pos': 6, 'log_pvalue': 1.61, 'rsid': 'rs6', 'trait': 'arithomania' }, - { 'chrom': 1, 'pos': 6, 'log_pvalue': 1.60, 'rsid': 'rs6', 'trait': 'graphomania' }, - ]; - const res = source.combineChainBody(exampleData, this.sampleChain, ['log_pvalue'], ['catalog:log_pvalue']); - assert.deepEqual(res, [ - { 'assoc:chromosome': 1, 'assoc:position': 2 }, // No annotations available for this point - { 'assoc:chromosome': 1, 'assoc:position': 4, 'catalog:log_pvalue': 1.41, 'n_catalog_matches': 2 }, - { 'assoc:chromosome': 1, 'assoc:position': 6, 'catalog:log_pvalue': 1.61, 'n_catalog_matches': 2 }, - ]); - }); - - it('gracefully handles no catalog entries in region', function () { - const source = new GwasCatalogLZ({ url: 'www.fake.test', params: { match_type: 'loose' } }); - const res = source.combineChainBody([], this.sampleChain, ['rsid', 'trait'], ['catalog:rsid', 'catalog:trait']); - assert.deepEqual(res, [ - { 'assoc:chromosome': 1, 'assoc:position': 2 }, - { 'assoc:chromosome': 1, 'assoc:position': 4 }, - { 'assoc:chromosome': 1, 'assoc:position': 6 }, - ]); - }); - }); -}); - diff --git a/test/unit/data/test_joins.js b/test/unit/data/test_joins.js new file mode 100644 index 00000000..e8a05135 --- /dev/null +++ b/test/unit/data/test_joins.js @@ -0,0 +1,102 @@ +/** + * Test custom join logic specific to LZ core code + */ + +import {assert} from 'chai'; + +import {JOINS} from '../../../esm/registry'; + + +describe('Custom join operations', function () { + describe('assoc_to_gwas_catalog', function () { + before(function() { + this.func = JOINS.get('assoc_to_gwas_catalog'); + }); + + beforeEach(function () { + this.assoc_data = [ + { 'assoc.chromosome': 1, 'assoc.position': 2 }, + { 'assoc.chromosome': 1, 'assoc.position': 4 }, + { 'assoc.chromosome': 1, 'assoc.position': 6 }, + ]; + this.catalog_data = [ + { 'catalog.chrom': 1, 'catalog.pos': 3, 'catalog.log_pvalue': 1.3, 'catalog.rsid': 'rs3', 'catalog.trait': 'arithomania' }, + { 'catalog.chrom': 1, 'catalog.pos': 4, 'catalog.log_pvalue': 1.4, 'catalog.rsid': 'rs4', 'catalog.trait': 'arithomania' }, + { 'catalog.chrom': 1, 'catalog.pos': 5, 'catalog.log_pvalue': 1.5, 'catalog.rsid': 'rs5', 'catalog.trait': 'arithomania' }, + { 'catalog.chrom': 1, 'catalog.pos': 6, 'catalog.log_pvalue': 1.6, 'catalog.rsid': 'rs6', 'catalog.trait': 'arithomania' }, + ]; + }); + + it('aligns records based on loose position match', function () { + const { func, assoc_data, catalog_data } = this; + const res = func(assoc_data, catalog_data, 'assoc.position', 'catalog.pos', 'catalog.log_pvalue'); + + assert.deepEqual(res, [ + { 'assoc.chromosome': 1, 'assoc.position': 2 }, // No annotations available for this point + { + 'assoc.chromosome': 1, + 'assoc.position': 4, + 'catalog.rsid': 'rs4', + 'catalog.trait': 'arithomania', + 'catalog.chrom': 1, + 'catalog.log_pvalue': 1.4, + 'catalog.pos': 4, + 'n_catalog_matches': 1, + }, + { + 'assoc.chromosome': 1, + 'assoc.position': 6, + 'catalog.rsid': 'rs6', + 'catalog.trait': 'arithomania', + 'catalog.chrom': 1, + 'catalog.log_pvalue': 1.6, + 'catalog.pos': 6, + 'n_catalog_matches': 1, + }, + ]); + }); + + it('handles the case where the same SNP has more than one catalog entry', function () { + const { assoc_data, func } = this; + const catalog_data = [ + { 'catalog.chrom': 1, 'catalog.pos': 4, 'catalog.log_pvalue': 1.40, 'catalog.rsid': 'rs4', 'catalog.trait': 'arithomania' }, + { 'catalog.chrom': 1, 'catalog.pos': 4, 'catalog.log_pvalue': 1.41, 'catalog.rsid': 'rs4', 'catalog.trait': 'graphomania' }, + { 'catalog.chrom': 1, 'catalog.pos': 6, 'catalog.log_pvalue': 1.61, 'catalog.rsid': 'rs6', 'catalog.trait': 'arithomania' }, + { 'catalog.chrom': 1, 'catalog.pos': 6, 'catalog.log_pvalue': 1.60, 'catalog.rsid': 'rs6', 'catalog.trait': 'graphomania' }, + ]; + + + const res = func(assoc_data, catalog_data, 'assoc.position', 'catalog.pos', 'catalog.log_pvalue'); + + assert.deepEqual(res, [ + { 'assoc.chromosome': 1, 'assoc.position': 2 }, // No annotations available for this point + { + 'assoc.chromosome': 1, + 'assoc.position': 4, + 'catalog.chrom': 1, + 'catalog.log_pvalue': 1.41, + 'catalog.pos': 4, + 'catalog.rsid': 'rs4', + 'catalog.trait': 'graphomania', + 'n_catalog_matches': 2, + }, + { + 'assoc.chromosome': 1, + 'assoc.position': 6, + 'catalog.chrom': 1, + 'catalog.log_pvalue': 1.61, + 'catalog.pos': 6, + 'catalog.rsid': 'rs6', + 'catalog.trait': 'arithomania', + 'n_catalog_matches': 2, + }, + ]); + }); + + it('gracefully handles no catalog entries in region', function () { + const { func, assoc_data } = this; + const res = func(assoc_data, [], 'assoc.position', 'catalog.pos', 'catalog.log_pvalue'); + assert.deepEqual(res, assoc_data); + }); + }); +}); From 0d8234737f8a47c27f1b7daf87a0289f4acea4f1 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 7 Sep 2021 17:46:43 -0400 Subject: [PATCH 010/100] Quick fixes for basic tests/ edge case --- esm/layouts/index.js | 2 +- esm/registry/joins.js | 4 +++- test/unit/data/test_sources.js | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/esm/layouts/index.js b/esm/layouts/index.js index cb0c9a68..e0525b3b 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -461,7 +461,7 @@ const annotation_catalog_layer = { type: 'assoc_to_gwas_catalog', name: 'combined', requires: ['assoc', 'catalog'], - params: ['assoc.variant', 'catalog.variant', 'catalog.log_pvalue'], + params: ['assoc.position', 'catalog.pos', 'catalog.log_pvalue'], }], id: 'annotation_catalog', type: 'annotation_track', diff --git a/esm/registry/joins.js b/esm/registry/joins.js index 696f38b8..d9ca23cd 100644 --- a/esm/registry/joins.js +++ b/esm/registry/joins.js @@ -39,8 +39,10 @@ registry.add('assoc_to_gwas_catalog', (assoc_data, catalog_data, assoc_key, cata let best = 0; let best_variant; for (let item of claims) { - if (item[catalog_logp_name] >= best) { + const val = item[catalog_logp_name]; + if ( val >= best) { best_variant = item; + best = val; } } best_variant.n_catalog_matches = claims.length; diff --git a/test/unit/data/test_sources.js b/test/unit/data/test_sources.js index 37f76d1f..7b29eae9 100644 --- a/test/unit/data/test_sources.js +++ b/test/unit/data/test_sources.js @@ -15,7 +15,7 @@ describe('DataSources object', function() { const data_sources = new DataSources(); const url = 'https://pheweb.org'; data_sources - .add('assoc', ['AssociationLZ', { url, params: {}}]); + .add('assoc', ['AssociationLZ', { url }]); const source = data_sources.get('assoc'); assert.instanceOf(source, AssociationLZ); assert.equal(url, source.url); @@ -29,7 +29,7 @@ describe('DataSources object', function() { }); it('can add source instances directly', function () { - const instance = new AssociationLZ({ url: 'https://pheweb.org', params: {}}); + const instance = new AssociationLZ({ url: 'https://pheweb.org' }); const data_sources = new DataSources(); data_sources .add('assoc', instance); @@ -38,8 +38,8 @@ describe('DataSources object', function() { }); it('should allow chainable adding with a fluent API', function () { - const instance1 = new AssociationLZ({ url: 1, params: {}}); - const instance2 = new AssociationLZ({ url: 2, params: {}}); + const instance1 = new AssociationLZ({ url: 1 }); + const instance2 = new AssociationLZ({ url: 2 }); const data_sources = new DataSources(); data_sources @@ -52,9 +52,9 @@ describe('DataSources object', function() { it('should ensure that all sources are aware of their namespace', function () { const data_sources = new DataSources(); - const instance = new AssociationLZ({ url: 1, params: {}}); + const instance = new AssociationLZ({ url: 1 }); data_sources - .add('assoc', ['AssociationLZ', { url: 1, params: {}}]) + .add('assoc', ['AssociationLZ', { url: 1 }]) .add('assoc2', instance); assert.equal(data_sources.get('assoc').source_id, 'assoc'); From 63881157c813095966d40dca0cda24fa17a62353 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 8 Sep 2021 00:13:47 -0400 Subject: [PATCH 011/100] Fixes for namespace syntax; add fields contract for more base sources [ci skip] --- esm/data/adapters.js | 36 +++--- esm/data/field.js | 39 +++--- esm/ext/lz-credible-sets.js | 46 +++---- esm/ext/lz-intervals-enrichment.js | 46 +++---- esm/ext/lz-intervals-track.js | 18 +-- esm/helpers/display.js | 2 +- esm/layouts/index.js | 114 ++++++++++-------- examples/credible_sets.html | 2 +- examples/multiple_phenotypes_layered.html | 2 +- examples/phewas_forest.html | 26 ++-- .../data_layer/test_highlight_regions.js | 18 +-- test/unit/components/test_datalayer.js | 25 ++-- test/unit/components/test_panel.js | 5 +- test/unit/components/test_plot.js | 9 +- test/unit/components/test_widgets.js | 3 +- test/unit/data/test_adapters.js | 27 +++-- test/unit/data/test_field.js | 29 ++--- test/unit/data/test_joins.js | 88 +++++++------- test/unit/data/test_requester.js | 8 +- test/unit/data/test_sources.js | 6 +- test/unit/ext/test_ext_intervals-track.js | 8 +- test/unit/helpers/test_display.js | 10 +- test/unit/test_layouts.js | 34 +++--- 23 files changed, 321 insertions(+), 280 deletions(-) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 5651b811..cce1d890 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -60,7 +60,7 @@ class BaseApiAdapter extends BaseAdapter {} class BaseLZAdapter extends BaseUrlAdapter { - constructor(config) { + constructor(config = {}) { super(config); this._validate_fields = true; @@ -108,7 +108,8 @@ class BaseLZAdapter extends BaseUrlAdapter { return Object.entries(row).reduce( (acc, [label, value]) => { if (!this._limit_fields || this._fields_contract.has(label)) { - acc[`${options._provider_name}.${label}`] = value; + // Rename API fields to format `namespace:fieldname` + acc[`${options._provider_name}:${label}`] = value; } return acc; }, @@ -129,7 +130,7 @@ class BaseLZAdapter extends BaseUrlAdapter { * @private */ _findPrefixedKey(a_record, fieldname) { - const suffixer = new RegExp(`\\.${fieldname}$`); + const suffixer = new RegExp(`:${fieldname}$`); const match = Object.keys(a_record).find((key) => suffixer.test(key)); if (!match) { throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`); @@ -140,7 +141,7 @@ class BaseLZAdapter extends BaseUrlAdapter { class BaseUMAdapter extends BaseLZAdapter { - constructor(config) { + constructor(config = {}) { super(config); // The UM portaldev API accepts an (optional) parameter "genome_build" this._genome_build = config.genome_build; @@ -195,17 +196,15 @@ class BaseUMAdapter extends BaseLZAdapter { class AssociationLZ extends BaseUMAdapter { - constructor(config) { + constructor(config = {}) { // Minimum adapter contract hard-codes fields contract based on UM PortalDev API + default assoc plot layout // For layers that require more functionality, pass extra_fields to source options config.fields = ['variant', 'position', 'log_pvalue', 'ref_allele']; super(config); + // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files const { source } = config; - if (!source) { - throw new Error('Association adapter must specify dataset ID via "source" option'); - } this._source_id = source; } @@ -239,7 +238,7 @@ class GwasCatalogLZ extends BaseUMAdapter { * @param {Number} [config.params.source] The ID of the chosen catalog. Most usages should omit this parameter and * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37. */ - constructor(config) { + constructor(config = {}) { config.fields = ['rsid', 'trait', 'log_pvalue']; super(config); } @@ -274,7 +273,7 @@ class GwasCatalogLZ extends BaseUMAdapter { * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37. */ class GeneLZ extends BaseUMAdapter { - constructor(config) { + constructor(config = {}) { super(config); // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given. @@ -318,7 +317,7 @@ class GeneConstraintLZ extends BaseLZAdapter { * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant. * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. */ - constructor(config) { + constructor(config = {}) { super(config); this._validate_fields = false; this._prefix_namespace = false; @@ -385,7 +384,7 @@ class GeneConstraintLZ extends BaseLZAdapter { class LDServer extends BaseUMAdapter { - constructor(config) { + constructor(config = {}) { // item1 = refvar, item2 = othervar config.fields = ['chromosome2', 'position2', 'variant2', 'correlation']; super(config); @@ -538,6 +537,11 @@ class LDServer extends BaseUMAdapter { * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37. */ class RecombLZ extends BaseUMAdapter { + constructor(config = {}) { + config.fields = ['position', 'recomb_rate']; + super(config); + } + /** * Add query parameters to the URL to construct a query for the specified region */ @@ -572,10 +576,14 @@ class RecombLZ extends BaseUMAdapter { * @param {object} data The data to be returned by this source (subject to namespacing rules) */ class StaticSource extends BaseLZAdapter { - constructor(config) { + constructor(config = {}) { // Does not receive any config; the only argument is the raw data, embedded when source is created super(...arguments); - this._data = config.data; + const { data } = config; + if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key + throw new Error("'StaticSource' must provide data as required option 'data'"); + } + this._data = data; } _performRequest(options) { diff --git a/esm/data/field.js b/esm/data/field.js index d97ad39a..0fb43c4b 100644 --- a/esm/data/field.js +++ b/esm/data/field.js @@ -1,4 +1,4 @@ -import transforms from '../registry/transforms'; +import TRANSFORMS from '../registry/transforms'; /** * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations. @@ -15,20 +15,18 @@ import transforms from '../registry/transforms'; */ class Field { constructor(field) { - const parts = /^(?:([^:]+):)?([^:|]*)(\|.+)*$/.exec(field); - /** @member {String} */ - this.full_name = field; - /** @member {String} */ - this.namespace = parts[1] || null; - /** @member {String} */ - this.name = parts[2] || null; - /** @member {Array} */ - this.transformations = []; - - if (typeof parts[3] == 'string' && parts[3].length > 1) { - this.transformations = parts[3].substring(1).split('|'); - this.transformations.forEach((transform, i) => this.transformations[i] = transforms.get(transform)); + // Two scenarios: we are requesting a field by full name, OR there are transforms to apply + // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN` + const field_pattern = /^(?:\w+:\w+|^\w+)(?:\|\w+)*$/; + if (!field_pattern.test(field)) { + throw new Error(`Invalid field specifier: '${field}'`); } + + const [name, ...transforms] = field.split('|'); + + this.full_name = field; // fieldname + transforms + this.field_name = name; // just fieldname + this.transformations = transforms.map((name) => TRANSFORMS.get(name)); } _applyTransformations(val) { @@ -48,15 +46,14 @@ class Field { * @returns {*} */ resolve(data, extra) { + // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null if (typeof data[this.full_name] == 'undefined') { // Check for cached result let val = null; - if (typeof (data[`${this.namespace}:${this.name}`]) != 'undefined') { // Fallback: value sans transforms - val = data[`${this.namespace}:${this.name}`]; - } else if (typeof data[this.name] != 'undefined') { // Fallback: value present without namespace - val = data[this.name]; - } else if (extra && typeof extra[this.full_name] != 'undefined') { // Fallback: check annotations - val = extra[this.full_name]; - } // We should really warn if no value found, but many bad layouts exist and this could break compatibility + if (data[this.field_name] !== undefined) { // Fallback: value sans transforms + val = data[this.field_name]; + } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations + val = extra[this.field_name]; + } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations) data[this.full_name] = this._applyTransformations(val); } return data[this.full_name]; diff --git a/esm/ext/lz-credible-sets.js b/esm/ext/lz-credible-sets.js index 2a2cb560..2840006f 100644 --- a/esm/ext/lz-credible-sets.js +++ b/esm/ext/lz-credible-sets.js @@ -56,7 +56,7 @@ function install (LocusZoom) { constructor(config) { super(...arguments); if (!(this._config.join_fields && this._config.join_fields.log_pvalue)) { - throw new Error(`Source config for ${this.constructor.name} must specify how to find 'fields.log_pvalue'`); + throw new Error(`Source config for ${this.constructor.name} must specify how to find 'fields:log_pvalue'`); } // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p) @@ -110,9 +110,9 @@ function install (LocusZoom) { // Annotate each response record based on credible set membership for (let i = 0; i < _assoc_data.length; i++) { - _assoc_data[i][`${options._provider_name}.posterior_prob`] = posteriorProbabilities[i]; - _assoc_data[i][`${options._provider_name}.contrib_fraction`] = credSetScaled[i]; - _assoc_data[i][`${options._provider_name}.is_member`] = credSetBool[i]; + _assoc_data[i][`${options._provider_name}:posterior_prob`] = posteriorProbabilities[i]; + _assoc_data[i][`${options._provider_name}:contrib_fraction`] = credSetScaled[i]; + _assoc_data[i][`${options._provider_name}:is_member`] = credSetBool[i]; } } catch (e) { // If the calculation cannot be completed, return the data without annotation fields @@ -134,7 +134,7 @@ function install (LocusZoom) { const association_credible_set_tooltip = function () { // Extend a known tooltip with an extra row of info showing posterior probabilities const l = LocusZoom.Layouts.get('tooltip', 'standard_association', { unnamespaced: true }); - l.html += '{{#if credset.posterior_prob}}
Posterior probability: {{credset.posterior_prob|scinotation|htmlescape}}{{/if}}'; + l.html += '{{#if credset:posterior_prob}}
Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}'; return l; }(); @@ -150,9 +150,9 @@ function install (LocusZoom) { closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: '{{assoc.variant|htmlescape}}
' - + 'P Value: {{assoc.log_pvalue|logtoscinotation|htmlescape}}
' + - '{{#if credset.posterior_prob}}
Posterior probability: {{credset.posterior_prob|scinotation|htmlescape}}{{/if}}', + html: '{{assoc:variant|htmlescape}}
' + + 'P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
' + + '{{#if credset:posterior_prob}}
Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}', }; LocusZoom.Layouts.add('tooltip', 'annotation_credible_set', annotation_credible_set_tooltip); @@ -171,14 +171,14 @@ function install (LocusZoom) { fill_opacity: 0.7, tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set', { unnamespaced: true }), fields: [ - 'assoc.variant', 'assoc.position', - 'assoc.log_pvalue', 'assoc.log_pvalue|logtoscinotation', - 'assoc.ref_allele', - 'credset.posterior_prob', 'credset.contrib_fraction', - 'credset.is_member', - 'ld.state', 'ld.isrefvar', + 'assoc:variant', 'assoc:position', + 'assoc:log_pvalue', 'assoc:log_pvalue|logtoscinotation', + 'assoc:ref_allele', + 'credset:posterior_prob', 'credset:contrib_fraction', + 'credset:is_member', + 'ld:state', 'ld:isrefvar', ], - match: { send: 'assoc.variant', receive: 'assoc.variant' }, + match: { send: 'assoc:variant', receive: 'assoc:variant' }, }); base.color.unshift({ field: 'lz_is_match', // Special field name whose presence triggers custom rendering @@ -202,9 +202,9 @@ function install (LocusZoom) { namespace: { 'assoc': 'assoc', 'credset(assoc)': 'credset' }, id: 'annotationcredibleset', type: 'annotation_track', - id_field: 'assoc.variant', + id_field: 'assoc:variant', x_axis: { - field: 'assoc.position', + field: 'assoc:position', }, color: [ { @@ -217,11 +217,11 @@ function install (LocusZoom) { }, '#00CC00', ], - fields: ['assoc.variant', 'assoc.position', 'assoc.log_pvalue', 'credset.posterior_prob', 'credset.contrib_fraction', 'credset.is_member'], - match: { send: 'assoc.variant', receive: 'assoc.variant' }, + fields: ['assoc:variant', 'assoc:position', 'assoc:log_pvalue', 'credset:posterior_prob', 'credset:contrib_fraction', 'credset:is_member'], + match: { send: 'assoc:variant', receive: 'assoc:variant' }, filters: [ // Specify which points to show on the track. Any selection must satisfy ALL filters - { field: 'credset.is_member', operator: '=', value: true }, + { field: 'credset:is_member', operator: '=', value: true }, ], behaviors: { onmouseover: [ @@ -306,7 +306,7 @@ function install (LocusZoom) { point_shape: 'circle', point_size: 40, color: { - field: 'credset.is_member', + field: 'credset:is_member', scale_function: 'if', parameters: { field_value: true, @@ -340,7 +340,7 @@ function install (LocusZoom) { point_size: 40, color: [ { - field: 'credset.contrib_fraction', + field: 'credset:contrib_fraction', scale_function: 'if', parameters: { field_value: 0, @@ -349,7 +349,7 @@ function install (LocusZoom) { }, { scale_function: 'interpolate', - field: 'credset.contrib_fraction', + field: 'credset:contrib_fraction', parameters: { breaks: [0, 1], values: ['#fafe87', '#9c0000'], diff --git a/esm/ext/lz-intervals-enrichment.js b/esm/ext/lz-intervals-enrichment.js index 055fe165..2c9b23af 100644 --- a/esm/ext/lz-intervals-enrichment.js +++ b/esm/ext/lz-intervals-enrichment.js @@ -157,10 +157,10 @@ function install(LocusZoom) { closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: `Tissue: {{intervals.tissueId|htmlescape}}
- Range: {{intervals.chromosome|htmlescape}}: {{intervals.start|htmlescape}}-{{intervals.end|htmlescape}}
- -log10 p: {{intervals.pValue|neglog10|scinotation|htmlescape}}
- Enrichment (n-fold): {{intervals.fold|scinotation|htmlescape}}`, + html: `Tissue: {{intervals:tissueId|htmlescape}}
+ Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
+ -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
+ Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}`, }; /** @@ -176,19 +176,19 @@ function install(LocusZoom) { id: 'intervals_enrichment', type: 'intervals_enrichment', tag: 'intervals_enrichment', - match: { send: 'intervals.tissueId' }, - fields: ['intervals.chromosome', 'intervals.start', 'intervals.end', 'intervals.pValue', 'intervals.fold', 'intervals.tissueId', 'intervals.ancestry'], - id_field: 'intervals.start', // not a good ID field for overlapping intervals - start_field: 'intervals.start', - end_field: 'intervals.end', + match: { send: 'intervals:tissueId' }, + fields: ['intervals:chromosome', 'intervals:start', 'intervals:end', 'intervals:pValue', 'intervals:fold', 'intervals:tissueId', 'intervals:ancestry'], + id_field: 'intervals:start', // not a good ID field for overlapping intervals + start_field: 'intervals:start', + end_field: 'intervals:end', filters: [ - { field: 'intervals.ancestry', operator: '=', value: 'EU' }, - { field: 'intervals.pValue', operator: '<=', value: 0.05 }, - { field: 'intervals.fold', operator: '>', value: 2.0 }, + { field: 'intervals:ancestry', operator: '=', value: 'EU' }, + { field: 'intervals:pValue', operator: '<=', value: 0.05 }, + { field: 'intervals:fold', operator: '>', value: 2.0 }, ], y_axis: { axis: 1, - field: 'intervals.fold', // is this used for other than extent generation? + field: 'intervals:fold', // is this used for other than extent generation? floor: 0, upper_buffer: 0.10, min_extent: [0, 10], @@ -196,7 +196,7 @@ function install(LocusZoom) { fill_opacity: 0.5, // Many intervals overlap: show all, even if the ones below can't be clicked color: [ { - field: 'intervals.tissueId', + field: 'intervals:tissueId', scale_function: 'stable_choice', parameters: { values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], @@ -227,19 +227,19 @@ function install(LocusZoom) { id: 'interval_matches', type: 'highlight_regions', namespace: { intervals: 'intervals' }, - match: { receive: 'intervals.tissueId' }, - fields: ['intervals.start', 'intervals.end', 'intervals.tissueId', 'intervals.ancestry', 'intervals.pValue', 'intervals.fold'], - start_field: 'intervals.start', - end_field: 'intervals.end', - merge_field: 'intervals.tissueId', + match: { receive: 'intervals:tissueId' }, + fields: ['intervals:start', 'intervals:end', 'intervals:tissueId', 'intervals:ancestry', 'intervals:pValue', 'intervals:fold'], + start_field: 'intervals:start', + end_field: 'intervals:end', + merge_field: 'intervals:tissueId', filters: [ { field: 'lz_is_match', operator: '=', value: true }, - { field: 'intervals.ancestry', operator: '=', value: 'EU' }, - { field: 'intervals.pValue', operator: '<=', value: 0.05 }, - { field: 'intervals.fold', operator: '>', value: 2.0 }, + { field: 'intervals:ancestry', operator: '=', value: 'EU' }, + { field: 'intervals:pValue', operator: '<=', value: 0.05 }, + { field: 'intervals:fold', operator: '>', value: 2.0 }, ], color: [{ - field: 'intervals.tissueId', + field: 'intervals:tissueId', scale_function: 'stable_choice', parameters: { values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index 84d6dda3..21db233b 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -607,7 +607,7 @@ function install (LocusZoom) { closable: false, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: '{{intervals.state_name|htmlescape}}
{{intervals.start|htmlescape}}-{{intervals.end|htmlescape}}', + html: '{{intervals:state_name|htmlescape}}
{{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}', }; /** @@ -622,23 +622,23 @@ function install (LocusZoom) { id: 'intervals', type: 'intervals', tag: 'intervals', - fields: ['intervals.start', 'intervals.end', 'intervals.state_id', 'intervals.state_name', 'intervals.itemRgb'], - id_field: 'intervals.start', // FIXME: This is not a good D3 "are these datums redundant" ID for datasets with multiple intervals heavily overlapping - start_field: 'intervals.start', - end_field: 'intervals.end', - track_split_field: 'intervals.state_name', - track_label_field: 'intervals.state_name', + fields: ['intervals:start', 'intervals:end', 'intervals:state_id', 'intervals:state_name', 'intervals:itemRgb'], + id_field: 'intervals:start', // FIXME: This is not a good D3 "are these datums redundant" ID for datasets with multiple intervals heavily overlapping + start_field: 'intervals:start', + end_field: 'intervals:end', + track_split_field: 'intervals:state_name', + track_label_field: 'intervals:state_name', split_tracks: false, always_hide_legend: true, color: [ { // If present, an explicit color field will override any other option (and be used to auto-generate legend) - field: 'intervals.itemRgb', + field: 'intervals:itemRgb', scale_function: 'to_rgb', }, { // TODO: Consider changing this to stable_choice in the future, for more stable coloring - field: 'intervals.state_name', + field: 'intervals:state_name', scale_function: 'categorical_bin', parameters: { // Placeholder. Empty categories and values will automatically be filled in when new data loads. diff --git a/esm/helpers/display.js b/esm/helpers/display.js index 2d0207c5..6f67afd5 100644 --- a/esm/helpers/display.js +++ b/esm/helpers/display.js @@ -160,7 +160,7 @@ function parseFields(html, data, extra) { // `tokens` is like [token,...] // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'} const tokens = []; - const regex = /{{(?:(#if )?([A-Za-z0-9_.|]+)|(#else)|(\/if))}}/; + const regex = /{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/; while (html.length > 0) { const m = regex.exec(html); if (!m) { diff --git a/esm/layouts/index.js b/esm/layouts/index.js index e0525b3b..5df77185 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -27,9 +27,9 @@ const standard_association_tooltip = { closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: `{{assoc.variant|htmlescape}}
- P Value: {{assoc.log_pvalue|logtoscinotation|htmlescape}}
- Ref. Allele: {{assoc.ref_allele|htmlescape}}
+ html: `{{assoc:variant|htmlescape}}
+ P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
+ Ref. Allele: {{assoc:ref_allele|htmlescape}}
{{#if lz_is_ld_refvar}}LD Reference Variant{{#else}} GWAS catalog / dbSNP', + + 'More: GWAS catalog / dbSNP', }; const coaccessibility_tooltip = { @@ -85,11 +85,11 @@ const coaccessibility_tooltip = { hide: { and: ['unhighlighted', 'unselected'] }, // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element) html: 'Regulatory element
' + - '{{access.start1|htmlescape}}-{{access.end1|htmlescape}}
' + + '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
' + 'Promoter
' + - '{{access.start2|htmlescape}}-{{access.end2|htmlescape}}
' + - '{{#if access.target}}Target: {{access.target|htmlescape}}
{{/if}}' + - 'Score: {{access.score|htmlescape}}', + '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
' + + '{{#if access:target}}Target: {{access:target|htmlescape}}
{{/if}}' + + 'Score: {{access:score|htmlescape}}', }; /* @@ -119,18 +119,18 @@ const recomb_rate_layer = { id: 'recombrate', type: 'line', tag: 'recombination', - fields: ['recomb.position', 'recomb.recomb_rate'], + fields: ['recomb:position', 'recomb:recomb_rate'], z_index: 1, style: { 'stroke': '#0000FF', 'stroke-width': '1.5px', }, x_axis: { - field: 'recomb.position', + field: 'recomb:position', }, y_axis: { axis: 2, - field: 'recomb.recomb_rate', + field: 'recomb:recomb_rate', floor: 0, ceiling: 100, }, @@ -148,13 +148,13 @@ const association_pvalues_layer = { type: 'left_match', name: 'combined', requires: ['assoc', 'ld'], - params: ['assoc.position', 'ld.position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc.variant = ld.variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later. + params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later. }], id: 'associationpvalues', type: 'scatter', tag: 'association', - fields: ['assoc.variant', 'assoc.position', 'assoc.log_pvalue', 'assoc.log_pvalue|logtoscinotation', 'assoc.ref_allele'], - id_field: 'assoc.variant', + fields: ['assoc:variant', 'assoc:position', 'assoc:log_pvalue', 'assoc:log_pvalue|logtoscinotation', 'assoc:ref_allele'], + id_field: 'assoc:variant', coalesce: { active: true, }, @@ -187,7 +187,7 @@ const association_pvalues_layer = { }, { scale_function: 'numerical_bin', - field: 'ld.correlation', + field: 'ld:correlation', parameters: { breaks: [0, 0.2, 0.4, 0.6, 0.8], // Derived from Google "Turbo" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85] @@ -208,11 +208,11 @@ const association_pvalues_layer = { label: null, z_index: 2, x_axis: { - field: 'assoc.position', + field: 'assoc:position', }, y_axis: { axis: 1, - field: 'assoc.log_pvalue', + field: 'assoc:log_pvalue', floor: 0, upper_buffer: 0.10, min_extent: [0, 10], @@ -241,11 +241,11 @@ const coaccessibility_layer = { id: 'coaccessibility', type: 'arcs', tag: 'coaccessibility', - fields: ['access.start1', 'access.end1', 'access.start2', 'access.end2', 'access.id', 'access.target', 'access.score'], - match: { send: 'access.target', receive: 'access.target' }, - id_field: 'access.id', + fields: ['access:start1', 'access:end1', 'access:start2', 'access:end2', 'access:id', 'access:target', 'access:score'], + match: { send: 'access:target', receive: 'access:target' }, + id_field: 'access:id', filters: [ - { field: 'access.score', operator: '!=', value: null }, + { field: 'access:score', operator: '!=', value: null }, ], color: [ { @@ -272,12 +272,12 @@ const coaccessibility_layer = { }, ], x_axis: { - field1: 'access.start1', - field2: 'access.start2', + field1: 'access:start1', + field2: 'access:start2', }, y_axis: { axis: 1, - field: 'access.score', + field: 'access:score', upper_buffer: 0.1, min_extent: [0, 1], }, @@ -304,9 +304,17 @@ const association_pvalues_catalog_layer = function () { // Slightly modify an existing layout let base = deepCopy(association_pvalues_layer); base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base); - base.tooltip.html += '{{#if catalog.rsid}}
See hits in GWAS catalog{{/if}}'; + + base.join_options.push({ + type: 'assoc_to_gwas_catalog', + name: 'assoc_catalog', + requires: ['combined', 'catalog'], + params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'], + }); + + base.tooltip.html += '{{#if catalog:rsid}}
See hits in GWAS catalog{{/if}}'; base.namespace.catalog = 'catalog'; - base.fields.push('catalog.rsid', 'catalog.trait', 'catalog.log_pvalue'); + base.fields.push('catalog:rsid', 'catalog:trait', 'catalog:log_pvalue'); return base; }(); @@ -323,22 +331,22 @@ const phewas_pvalues_layer = { point_shape: 'circle', point_size: 70, tooltip_positioning: 'vertical', - id_field: 'phewas.id', - fields: ['phewas.id', 'phewas.log_pvalue', 'phewas.trait_group', 'phewas.trait_label'], + id_field: 'phewas:id', + fields: ['phewas:id', 'phewas:log_pvalue', 'phewas:trait_group', 'phewas:trait_label'], x_axis: { - field: 'phewas.x', // Synthetic/derived field added by `category_scatter` layer - category_field: 'phewas.trait_group', + field: 'phewas:x', // Synthetic/derived field added by `category_scatter` layer + category_field: 'phewas:trait_group', lower_buffer: 0.025, upper_buffer: 0.025, }, y_axis: { axis: 1, - field: 'phewas.log_pvalue', + field: 'phewas:log_pvalue', floor: 0, upper_buffer: 0.15, }, color: [{ - field: 'phewas.trait_group', + field: 'phewas:trait_group', scale_function: 'categorical_bin', parameters: { categories: [], @@ -352,9 +360,9 @@ const phewas_pvalues_layer = { show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, html: [ - 'Trait: {{phewas.trait_label|htmlescape}}
', - 'Trait Category: {{phewas.trait_group|htmlescape}}
', - 'P-value: {{phewas.log_pvalue|logtoscinotation|htmlescape}}
', + 'Trait: {{phewas:trait_label|htmlescape}}
', + 'Trait Category: {{phewas:trait_group|htmlescape}}
', + 'P-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}
', ].join(''), }, behaviors: { @@ -369,7 +377,7 @@ const phewas_pvalues_layer = { ], }, label: { - text: '{{phewas.trait_label}}', + text: '{{phewas:trait_label}}', spacing: 6, lines: { style: { @@ -380,7 +388,7 @@ const phewas_pvalues_layer = { }, filters: [ { - field: 'phewas.log_pvalue', + field: 'phewas:log_pvalue', operator: '>=', value: 20, }, @@ -461,25 +469,25 @@ const annotation_catalog_layer = { type: 'assoc_to_gwas_catalog', name: 'combined', requires: ['assoc', 'catalog'], - params: ['assoc.position', 'catalog.pos', 'catalog.log_pvalue'], + params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'], }], id: 'annotation_catalog', type: 'annotation_track', tag: 'gwascatalog', - id_field: 'assoc.variant', + id_field: 'assoc:variant', x_axis: { - field: 'assoc.position', + field: 'assoc:position', }, color: '#0000CC', fields: [ - 'assoc.variant', 'assoc.chromosome', 'assoc.position', - 'catalog.variant', 'catalog.rsid', 'catalog.trait', - 'catalog.log_pvalue', 'catalog.pos', + 'assoc:variant', 'assoc:chromosome', 'assoc:position', + 'catalog:variant', 'catalog:rsid', 'catalog:trait', + 'catalog:log_pvalue', 'catalog:pos', ], filters: [ // Specify which points to show on the track. Any selection must satisfy ALL filters - { field: 'catalog.rsid', operator: '!=', value: null }, - { field: 'catalog.log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, + { field: 'catalog:rsid', operator: '!=', value: null }, + { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, ], behaviors: { onmouseover: [ @@ -785,7 +793,7 @@ const association_catalog_panel = function () { let base = deepCopy(association_panel); base = merge({ id: 'associationcatalog', - namespace: { 'assoc': 'assoc', 'ld': 'ld', 'catalog': 'catalog' }, // Required to resolve display options + namespace: { 'assoc': 'assoc', 'ld(assoc)': 'ld', 'catalog': 'catalog' }, // Required to resolve display options }, base); base.toolbar.widgets.push({ @@ -805,7 +813,7 @@ const association_catalog_panel = function () { display_name: 'Label catalog traits', // Human readable representation of field name display: { // Specify layout directives that control display of the plot for this option label: { - text: '{{catalog.trait}}', + text: '{{catalog:trait}}', spacing: 6, lines: { style: { @@ -817,9 +825,9 @@ const association_catalog_panel = function () { filters: [ // Only label points if they are significant for some trait in the catalog, AND in high LD // with the top hit of interest - { field: 'catalog.trait', operator: '!=', value: null }, - { field: 'catalog.log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, - { field: 'ld.state', operator: '>', value: 0.4 }, + { field: 'catalog:trait', operator: '!=', value: null }, + { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, + { field: 'ld:state', operator: '>', value: 0.4 }, ], style: { 'font-size': '10px', diff --git a/examples/credible_sets.html b/examples/credible_sets.html index 14a47d4a..580d3f45 100644 --- a/examples/credible_sets.html +++ b/examples/credible_sets.html @@ -92,7 +92,7 @@

Top Hits

source: 45, id_field: "variant", }]) - .add("credset", ["CredibleSetLZ", { join_fields: { log_pvalue: 'assoc.log_pvalue' }, threshold: 0.95, significance_threshold: 7.301 }]) + .add("credset", ["CredibleSetLZ", { join_fields: { log_pvalue: 'assoc:log_pvalue' }, threshold: 0.95, significance_threshold: 7.301 }]) .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', build: 'GRCh37', population: 'ALL' }]) .add("gene", ["GeneLZ", {url: apiBase + "annotation/genes/", build: 'GRCh37' }]) .add("recomb", ["RecombLZ", {url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) diff --git a/examples/multiple_phenotypes_layered.html b/examples/multiple_phenotypes_layered.html index 23d71b03..be693eaa 100644 --- a/examples/multiple_phenotypes_layered.html +++ b/examples/multiple_phenotypes_layered.html @@ -106,7 +106,7 @@

Top Hits

phenos.forEach(function(pheno){ data_sources.add(pheno.namespace, ["AssociationLZ", {url: apiBase + "statistic/single/", source: pheno.study_id, id_field: "variant" }]); var association_data_layer_mods = { - // FIXME: implement dependency fetcher so that this demo doesn't pull LD when it shouldn't + // FIXME: Merge logic prevents us from removing a namespace- LD is still present after override! namespace: { "assoc": pheno.namespace }, join_options: [], id: "associationpvalues_" + pheno.namespace, diff --git a/examples/phewas_forest.html b/examples/phewas_forest.html index c98e5745..39233b0c 100644 --- a/examples/phewas_forest.html +++ b/examples/phewas_forest.html @@ -102,7 +102,7 @@
< return home
point_shape: "square", point_size: { scale_function: "interpolate", - field: "phewas.log_pvalue", + field: 'phewas:log_pvalue', parameters: { breaks: [0, 10], values: [60, 160], @@ -111,7 +111,7 @@
< return home
}, color: { scale_function: "interpolate", - field: "phewas.log_pvalue", + field: 'phewas:log_pvalue', parameters: { breaks: [0, 10], values: ["#fee8c8","#b30000"], @@ -127,21 +127,21 @@
< return home
{ shape: "square", class: "lz-data_layer-forest", color: "#d7301f", size: 140, label: "8 - 10" }, { shape: "square", class: "lz-data_layer-forest", color: "#b30000", size: 160, label: "10+" } ], - id_field: "phewas.phenotype", - fields: ["phewas.phenotype", "phewas.log_pvalue", "phewas.log_pvalue|logtoscinotation", "phewas.beta", "phewas.ci_start", "phewas.ci_end"], + id_field: 'phewas:phenotype', + fields: ['phewas:phenotype', 'phewas:log_pvalue', "phewas:log_pvalue|logtoscinotation", 'phewas:beta', 'phewas:ci_start', 'phewas:ci_end'], x_axis: { - field: "phewas.beta", + field: 'phewas:beta', lower_buffer: 0.1, upper_buffer: 0.1 }, y_axis: { axis: 2, - category_field: "phewas.phenotype", // Labels - field: "phewas.y_offset", // Positions (dynamically added) + category_field: 'phewas:phenotype', // Labels + field: 'phewas:y_offset', // Positions (dynamically added) }, confidence_intervals: { - start_field: "phewas.ci_start", - end_field: "phewas.ci_end" + start_field: 'phewas:ci_start', + end_field: 'phewas:ci_end' }, behaviors: { onmouseover: [ @@ -162,10 +162,10 @@
< return home
closable: true, show: { or: ["highlighted", "selected"] }, hide: { and: ["unhighlighted", "unselected"] }, - html: "{{phewas.phenotype|htmlescape}}
" - + "P Value: {{phewas.log_pvalue|logtoscinotation|htmlescape}}
" - + "Odds Ratio: {{phewas.beta|htmlescape}}
" - + "95% Conf. Interval: [ {{phewas.ci_start|htmlescape}} {{phewas.ci_end|htmlescape}} ]" + html: "{{phewas:phenotype|htmlescape}}
" + + "P Value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}
" + + "Odds Ratio: {{phewas:beta|htmlescape}}
" + + "95% Conf. Interval: [ {{phewas:ci_start|htmlescape}} {{phewas:ci_end|htmlescape}} ]" } }, { diff --git a/test/unit/components/data_layer/test_highlight_regions.js b/test/unit/components/data_layer/test_highlight_regions.js index 9037d917..41323edc 100644 --- a/test/unit/components/data_layer/test_highlight_regions.js +++ b/test/unit/components/data_layer/test_highlight_regions.js @@ -76,7 +76,7 @@ describe('highlight_regions data layer', function () { }; d3.select('body').append('div').attr('id', 'plot'); const sources = new DataSources() - .add('intervals', ['StaticJSON', [{start: 1, end: 2}]]); + .add('intervals', ['StaticJSON', { data: [{start: 1, end: 2}] }]); this.plot = populate('#plot', sources, layout); }); afterEach(function() { @@ -90,13 +90,17 @@ describe('highlight_regions data layer', function () { }); it('can render data from regions in data source', function () { // Rejigger the original plot layout to work in "datasource mode" - const d_layout = this.plot.panels.p.data_layers.d.layout; - d_layout.start_field = 'intervals:start'; - d_layout.end_field = 'intervals:end'; - d_layout.regions = []; - d_layout.fields = ['intervals:start', 'intervals:end']; + const layer = this.plot.panels.p.data_layers.d; + const d_layout = layer.layout; + Object.assign(d_layout, { + namespace: { intervals: 'intervals'}, + start_field: 'intervals:start', + end_field: 'intervals:end', + regions: [], + fields: ['intervals:start', 'intervals:end'], + }); return this.plot.applyState().then(() => { - assert.equal(this.plot.panels.p.data_layers.d.svg.group.selectAll('rect').size(), 1, 'Layer draws one region as pulled from the datasource'); + assert.equal(layer.svg.group.selectAll('rect').size(), 1, 'Layer draws one region as pulled from the datasource'); }); }); }); diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index bb9a736f..6428fe82 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -425,13 +425,14 @@ describe('LocusZoom.DataLayer', function () { beforeEach(function () { this.plot = null; const data_sources = new DataSources() - .add('d', ['StaticJSON', [{ id: 'a' }, { id: 'b' }, { id: 'c' }]]); + .add('d', ['StaticJSON', { data: [{ id: 'a' }, { id: 'b' }, { id: 'c' }] }]); const layout = { panels: [ { id: 'p', data_layers: [ { + namespace: { d: 'd' }, id: 'd', fields: ['d:id'], id_field: 'd:id', @@ -450,7 +451,7 @@ describe('LocusZoom.DataLayer', function () { delete this.plot; }); it('should allow for highlighting and unhighlighting a single element', function () { - return this.plot.lzd.getData({}, ['d:id']) + return this.plot.lzd.getData({}, { d: 'd' }, []) .then(() => { const state_id = this.plot.panels.p.data_layers.d.state_id; const layer_state = this.plot.state[state_id]; @@ -483,7 +484,7 @@ describe('LocusZoom.DataLayer', function () { }); }); it('should allow for highlighting and unhighlighting all elements', function () { - return this.plot.lzd.getData({}, ['d:id']) + return this.plot.lzd.getData({}, { d: 'd' }, []) .then(() => { const state_id = this.plot.panels.p.data_layers.d.state_id; const layer_state = this.plot.state[state_id]; @@ -509,7 +510,7 @@ describe('LocusZoom.DataLayer', function () { beforeEach(function () { this.plot = null; const data_sources = new DataSources() - .add('d', ['StaticJSON', [{ id: 'a' }, { id: 'b' }, { id: 'c' }]]); + .add('d', ['StaticJSON', { data: [{ id: 'a' }, { id: 'b' }, { id: 'c' }] }]); const layout = { panels: [ { @@ -517,6 +518,7 @@ describe('LocusZoom.DataLayer', function () { data_layers: [ { id: 'd', + namespace: { d: 'd' }, fields: ['d:id'], id_field: 'd:id', type: 'scatter', @@ -534,7 +536,7 @@ describe('LocusZoom.DataLayer', function () { delete this.plot; }); it('should allow for selecting and unselecting a single element', function () { - return this.plot.lzd.getData({}, ['d:id']) + return this.plot.lzd.getData({}, { d: 'd' }, []) .then(() => { const state_id = this.plot.panels.p.data_layers.d.state_id; const layer_state = this.plot.state[state_id]; @@ -567,7 +569,7 @@ describe('LocusZoom.DataLayer', function () { }); }); it('should allow for selecting and unselecting all elements', function () { - return this.plot.lzd.getData({}, ['d:id']) + return this.plot.lzd.getData({}, { d: 'd' }, []) .then(() => { const state_id = this.plot.panels.p.data_layers.d.state_id; const layer_state = this.plot.state[state_id]; @@ -715,7 +717,7 @@ describe('LocusZoom.DataLayer', function () { describe('data element behaviors', function () { beforeEach(function() { const data_sources = new DataSources() - .add('d', ['StaticJSON', [{ id: 'a', x: 1, y: 2 }, { id: 'b', x: 2, y:0 }, { id: 'c', x: 3, y:1 }]]); + .add('d', ['StaticJSON', { data: [{ id: 'a', x: 1, y: 2 }, { id: 'b', x: 2, y:0 }, { id: 'c', x: 3, y:1 }] }]); const layout = { panels: [ @@ -724,6 +726,7 @@ describe('LocusZoom.DataLayer', function () { data_layers: [ { id: 'd', + namespace: { d: 'd'}, type: 'scatter', fields: ['d:id', 'd:x', 'd:y'], id_field: 'd:id', @@ -884,7 +887,9 @@ describe('LocusZoom.DataLayer', function () { beforeEach(function () { this.plot = null; const data_sources = new DataSources() - .add('d', ['StaticJSON', [{ id: 1, a: 12 }, { id: 2, a: 11 }, { id: 3, a: 13 }, { id: 4, a: 15 }, { id: 5, a: 14 }]]); + .add('d', ['StaticJSON', { + data: [{ id: 1, a: 12 }, { id: 2, a: 11 }, { id: 3, a: 13 }, { id: 4, a: 15 }, { id: 5, a: 14 }] + }]); const layout = { panels: [ { @@ -892,6 +897,7 @@ describe('LocusZoom.DataLayer', function () { data_layers: [ { id: 'd', + namespace: { d: 'd'}, fields: ['d:id', 'd:a'], id_field: 'd:id', type: 'scatter', @@ -970,7 +976,7 @@ describe('LocusZoom.DataLayer', function () { beforeEach(function () { this.plot = null; const data_sources = new DataSources() - .add('d', ['StaticJSON', [{ id: 'a' }, { id: 'b', some_field: true }, { id: 'c' }]]); + .add('d', ['StaticJSON', { data: [{ id: 'a' }, { id: 'b', some_field: true }, { id: 'c' }] }]); const layout = { panels: [ { @@ -978,6 +984,7 @@ describe('LocusZoom.DataLayer', function () { data_layers: [ { id: 'd', + namespace: { d: 'd'}, fields: ['d:id', 'd:some_field'], id_field: 'd:id', type: 'scatter', diff --git a/test/unit/components/test_panel.js b/test/unit/components/test_panel.js index 8d48cab5..8e15d5d5 100644 --- a/test/unit/components/test_panel.js +++ b/test/unit/components/test_panel.js @@ -310,7 +310,9 @@ describe('Panel', function() { beforeEach(function() { this.plot = null; this.datasources = new DataSources() - .add('static', ['StaticJSON', [{ id: 'a', x: 1, y: 2 }, { id: 'b', x: 3, y: 4 }, { id: 'c', x: 5, y: 6 }] ]); + .add('static', ['StaticJSON', { + data: [{ id: 'a', x: 1, y: 2 }, { id: 'b', x: 3, y: 4 }, { id: 'c', x: 5, y: 6 }], + }]); this.layout = { width: 100, panels: [ @@ -326,6 +328,7 @@ describe('Panel', function() { { id: 'd', type: 'scatter', + namespace: { static: 'static' }, fields: ['static:id', 'static:x', 'static:y'], id_field: 'static:id', z_index: 0, diff --git a/test/unit/components/test_plot.js b/test/unit/components/test_plot.js index f8d815c1..604674f6 100644 --- a/test/unit/components/test_plot.js +++ b/test/unit/components/test_plot.js @@ -389,7 +389,7 @@ describe('LocusZoom.Plot', function() { }); }); - describe('subscribeToData', function() { + describe.skip('subscribeToData', function() { beforeEach(function() { const layout = { panels: [{ id: 'panel0' }], @@ -397,7 +397,7 @@ describe('LocusZoom.Plot', function() { this.first_source_data = [ { x: 0, y: false }, { x: 1, y: true } ]; this.data_sources = new DataSources() - .add('first', ['StaticJSON', this.first_source_data ]); + .add('first', ['StaticJSON', {data: this.first_source_data }]); d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', this.data_sources, layout); @@ -552,7 +552,7 @@ describe('LocusZoom.Plot', function() { this.plot = null; const first_source_data = [{ id: 'a', x: 0, y: false }, { id: 'b', x: 1, y: true }]; const data_sources = new DataSources() - .add('s', ['StaticJSON', first_source_data]); + .add('s', ['StaticJSON', { data: first_source_data }]); this.layout = { panels: [ { @@ -562,6 +562,7 @@ describe('LocusZoom.Plot', function() { id: 'd1', id_field: 's:id', type: 'scatter', + namespace: { s: 's' }, fields: ['s:id', 's:x'], match: { send: 's:x', receive: 's:x' }, }, @@ -569,12 +570,14 @@ describe('LocusZoom.Plot', function() { id: 'd2', id_field: 's:id', type: 'scatter', + namespace: { s: 's' }, fields: ['s:id', 's:x', 's:y'], }, { id: 'd3', id_field: 's:id', type: 'scatter', + namespace: { s: 's' }, fields: ['s:id', 's:y'], match: { receive: 's:y' }, }, diff --git a/test/unit/components/test_widgets.js b/test/unit/components/test_widgets.js index ed9a3bc4..931f35f2 100644 --- a/test/unit/components/test_widgets.js +++ b/test/unit/components/test_widgets.js @@ -41,7 +41,7 @@ describe('Toolbar widgets', function () { describe('Filter fields Widget', function () { beforeEach(function() { const datasources = new DataSources() - .add('d', ['StaticJSON', [{ id: 1, a: 12 }]]); + .add('d', ['StaticJSON', {data: [{ id: 1, a: 12 }] }]); const layout = { panels: [{ id: 'p', @@ -61,6 +61,7 @@ describe('Toolbar widgets', function () { data_layers: [ { id: 'd', + namespace: {d: 'd'}, fields: ['d:id', 'd:a'], id_field: 'd:id', type: 'scatter', diff --git a/test/unit/data/test_adapters.js b/test/unit/data/test_adapters.js index 3263ac97..10904a50 100644 --- a/test/unit/data/test_adapters.js +++ b/test/unit/data/test_adapters.js @@ -62,7 +62,7 @@ describe('Data adapters', function () { return source.getData({ _provider_name: 'sometest', _test_data: [{ a: 1, b: 2 }]} - ).then((result) => assert.deepEqual(result, [{'sometest.a': 1, 'sometest.b': 2}])); + ).then((result) => assert.deepEqual(result, [{'sometest:a': 1, 'sometest:b': 2}])); }); it('can restrict the list of returned fields to those in the fields contract', function () { @@ -70,17 +70,17 @@ describe('Data adapters', function () { return source.getData({ _provider_name: 'sometest', _test_data: [{ a: 1, b: 2 }]} - ).then((result) => assert.deepEqual(result, [{ 'sometest.b': 2}])); + ).then((result) => assert.deepEqual(result, [{ 'sometest:b': 2}])); }); it('has a helper to locate dependent fields that were already namespaced', function () { const source = new BaseTestClass({}); - const match = source._findPrefixedKey({'sometest.aaa': 1, 'sometest.a': 2, 'sometest.aa': 3}, 'a'); + const match = source._findPrefixedKey({'sometest:aaa': 1, 'sometest:a': 2, 'sometest:aa': 3}, 'a'); - assert.equal(match, 'sometest.a', 'Found correct key and ignored partial suffixes'); + assert.equal(match, 'sometest:a', 'Found correct key and ignored partial suffixes'); assert.throws( - () => source._findPrefixedKey([{'sometest.aaa': 1 }], 'no_such_key'), + () => source._findPrefixedKey([{'sometest:aaa': 1 }], 'no_such_key'), /Could not locate/, 'Pedantically insists that data match the expected contract' ); @@ -150,6 +150,17 @@ describe('Data adapters', function () { }); describe('StaticSource', function () { + it('warns if the `data` key is missing', function () { + assert.throws( + () => new StaticSource([]), + /required option/ + ); + assert.throws( + () => new StaticSource({}), + /required option/ + ); + }); + it('Returns the exact data provided by the user, regardless of region', function () { const data = { a: 1, b: 2, c: 3 }; const source = new StaticSource({ data }); @@ -168,9 +179,9 @@ describe('Data adapters', function () { beforeEach(function () { this._assoc_data = [ // Deliberately use several variant formats to verify normalization - { 'assoc.variant': '1:23_A/C', 'assoc.log_pvalue': 0.2 }, - { 'assoc.variant': '1:24:A:C', 'assoc.log_pvalue': 125 }, - { 'assoc.variant': '1-25-A-C', 'assoc.log_pvalue': 72 }, + { 'assoc:variant': '1:23_A/C', 'assoc:log_pvalue': 0.2 }, + { 'assoc:variant': '1:24:A:C', 'assoc:log_pvalue': 125 }, + { 'assoc:variant': '1-25-A-C', 'assoc:log_pvalue': 72 }, ]; }); diff --git a/test/unit/data/test_field.js b/test/unit/data/test_field.js index 64e97eb2..57f163cb 100644 --- a/test/unit/data/test_field.js +++ b/test/unit/data/test_field.js @@ -5,58 +5,53 @@ describe('Field resolver', function () { it('should correctly parse name-only field string into components', function () { const f = new Field('foo'); assert.equal(f.full_name, 'foo'); - assert.equal(f.name, 'foo'); - assert.equal(f.namespace, null); + assert.equal(f.field_name, 'foo'); assert.isArray(f.transformations); assert.equal(f.transformations.length, 0); }); + it('should correctly parse namespaced field string into components', function () { const f = new Field('foo:bar'); assert.equal(f.full_name, 'foo:bar'); - assert.equal(f.name, 'bar'); - assert.equal(f.namespace, 'foo'); + assert.equal(f.field_name, 'foo:bar'); assert.isArray(f.transformations); assert.equal(f.transformations.length, 0); }); + it('should correctly parse namespaced field string with single transformation into components', function () { const f = new Field('foo:bar|scinotation'); assert.equal(f.full_name, 'foo:bar|scinotation'); - assert.equal(f.name, 'bar'); - assert.equal(f.namespace, 'foo'); + assert.equal(f.field_name, 'foo:bar'); assert.isArray(f.transformations); assert.equal(f.transformations.length, 1); assert.isFunction(f.transformations[0]); }); + it('should correctly parse namespaced field string with multiple transformations into components', function () { const f = new Field('foo:bar|scinotation|htmlescape'); assert.equal(f.full_name, 'foo:bar|scinotation|htmlescape'); - assert.equal(f.name, 'bar'); - assert.equal(f.namespace, 'foo'); + assert.equal(f.field_name, 'foo:bar'); assert.isArray(f.transformations); assert.equal(f.transformations.length, 2); assert.isFunction(f.transformations[0]); assert.isFunction(f.transformations[1]); }); + it('should resolve a value when passed a data object', function () { const d = { 'foo:bar': 123 }; const f = new Field('foo:bar'); const v = f.resolve(d); assert.equal(v, 123); }); - it('should resolve to an unnamespaced value if its present and the explicitly namespaced value is not, and cache the value for future lookups', function () { - const d = { 'bar': 123 }; - const f = new Field('foo:bar'); - const v = f.resolve(d); - assert.equal(v, 123); - assert.equal(d['foo:bar'], 123); - }); + it('should use annotations (extra_fields) by exact field name, iff no value is present in point data', function () { - const d = { 'bar': 123, 'foo:my_annotation': 13 }; + const d = { 'foo:bar': 123, 'foo:my_annotation': 13 }; const f = new Field('my_annotation'); const v = f.resolve(d, { 'my_annotation': 12 }); assert.equal(v, 12); }); + it('should apply arbitrarily many transformations in the order defined', function () { const d = { 'foo:bar': 123 }; const f = new Field('foo:bar|neglog10|htmlescape|urlencode'); @@ -64,12 +59,14 @@ describe('Field resolver', function () { assert.equal(v, '-2.0899051114393976'); assert.equal(d['foo:bar|neglog10|htmlescape|urlencode'], '-2.0899051114393976', 'Value is cached for future lookups'); }); + it('should compose two transformations of the same type', function () { const d = { 'foo:pvalue': 10 }; const f = new Field('foo:pvalue|log10|log10'); const v = f.resolve(d); assert.equal(v, 0); }); + it('should compose two different numeric transformations left to right', function () { const d = { 'foo:pvalue': 0.5 }; const f = new Field('foo:pvalue|neglog10|log10'); diff --git a/test/unit/data/test_joins.js b/test/unit/data/test_joins.js index e8a05135..b3d0c6d8 100644 --- a/test/unit/data/test_joins.js +++ b/test/unit/data/test_joins.js @@ -15,42 +15,42 @@ describe('Custom join operations', function () { beforeEach(function () { this.assoc_data = [ - { 'assoc.chromosome': 1, 'assoc.position': 2 }, - { 'assoc.chromosome': 1, 'assoc.position': 4 }, - { 'assoc.chromosome': 1, 'assoc.position': 6 }, + { 'assoc:chromosome': 1, 'assoc:position': 2 }, + { 'assoc:chromosome': 1, 'assoc:position': 4 }, + { 'assoc:chromosome': 1, 'assoc:position': 6 }, ]; this.catalog_data = [ - { 'catalog.chrom': 1, 'catalog.pos': 3, 'catalog.log_pvalue': 1.3, 'catalog.rsid': 'rs3', 'catalog.trait': 'arithomania' }, - { 'catalog.chrom': 1, 'catalog.pos': 4, 'catalog.log_pvalue': 1.4, 'catalog.rsid': 'rs4', 'catalog.trait': 'arithomania' }, - { 'catalog.chrom': 1, 'catalog.pos': 5, 'catalog.log_pvalue': 1.5, 'catalog.rsid': 'rs5', 'catalog.trait': 'arithomania' }, - { 'catalog.chrom': 1, 'catalog.pos': 6, 'catalog.log_pvalue': 1.6, 'catalog.rsid': 'rs6', 'catalog.trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 3, 'catalog:log_pvalue': 1.3, 'catalog:rsid': 'rs3', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 4, 'catalog:log_pvalue': 1.4, 'catalog:rsid': 'rs4', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 5, 'catalog:log_pvalue': 1.5, 'catalog:rsid': 'rs5', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 6, 'catalog:log_pvalue': 1.6, 'catalog:rsid': 'rs6', 'catalog:trait': 'arithomania' }, ]; }); it('aligns records based on loose position match', function () { const { func, assoc_data, catalog_data } = this; - const res = func(assoc_data, catalog_data, 'assoc.position', 'catalog.pos', 'catalog.log_pvalue'); + const res = func(assoc_data, catalog_data, 'assoc:position', 'catalog:pos', 'catalog:log_pvalue'); assert.deepEqual(res, [ - { 'assoc.chromosome': 1, 'assoc.position': 2 }, // No annotations available for this point + { 'assoc:chromosome': 1, 'assoc:position': 2 }, // No annotations available for this point { - 'assoc.chromosome': 1, - 'assoc.position': 4, - 'catalog.rsid': 'rs4', - 'catalog.trait': 'arithomania', - 'catalog.chrom': 1, - 'catalog.log_pvalue': 1.4, - 'catalog.pos': 4, + 'assoc:chromosome': 1, + 'assoc:position': 4, + 'catalog:rsid': 'rs4', + 'catalog:trait': 'arithomania', + 'catalog:chrom': 1, + 'catalog:log_pvalue': 1.4, + 'catalog:pos': 4, 'n_catalog_matches': 1, }, { - 'assoc.chromosome': 1, - 'assoc.position': 6, - 'catalog.rsid': 'rs6', - 'catalog.trait': 'arithomania', - 'catalog.chrom': 1, - 'catalog.log_pvalue': 1.6, - 'catalog.pos': 6, + 'assoc:chromosome': 1, + 'assoc:position': 6, + 'catalog:rsid': 'rs6', + 'catalog:trait': 'arithomania', + 'catalog:chrom': 1, + 'catalog:log_pvalue': 1.6, + 'catalog:pos': 6, 'n_catalog_matches': 1, }, ]); @@ -59,35 +59,35 @@ describe('Custom join operations', function () { it('handles the case where the same SNP has more than one catalog entry', function () { const { assoc_data, func } = this; const catalog_data = [ - { 'catalog.chrom': 1, 'catalog.pos': 4, 'catalog.log_pvalue': 1.40, 'catalog.rsid': 'rs4', 'catalog.trait': 'arithomania' }, - { 'catalog.chrom': 1, 'catalog.pos': 4, 'catalog.log_pvalue': 1.41, 'catalog.rsid': 'rs4', 'catalog.trait': 'graphomania' }, - { 'catalog.chrom': 1, 'catalog.pos': 6, 'catalog.log_pvalue': 1.61, 'catalog.rsid': 'rs6', 'catalog.trait': 'arithomania' }, - { 'catalog.chrom': 1, 'catalog.pos': 6, 'catalog.log_pvalue': 1.60, 'catalog.rsid': 'rs6', 'catalog.trait': 'graphomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 4, 'catalog:log_pvalue': 1.40, 'catalog:rsid': 'rs4', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 4, 'catalog:log_pvalue': 1.41, 'catalog:rsid': 'rs4', 'catalog:trait': 'graphomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 6, 'catalog:log_pvalue': 1.61, 'catalog:rsid': 'rs6', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 6, 'catalog:log_pvalue': 1.60, 'catalog:rsid': 'rs6', 'catalog:trait': 'graphomania' }, ]; - const res = func(assoc_data, catalog_data, 'assoc.position', 'catalog.pos', 'catalog.log_pvalue'); + const res = func(assoc_data, catalog_data, 'assoc:position', 'catalog:pos', 'catalog:log_pvalue'); assert.deepEqual(res, [ - { 'assoc.chromosome': 1, 'assoc.position': 2 }, // No annotations available for this point + { 'assoc:chromosome': 1, 'assoc:position': 2 }, // No annotations available for this point { - 'assoc.chromosome': 1, - 'assoc.position': 4, - 'catalog.chrom': 1, - 'catalog.log_pvalue': 1.41, - 'catalog.pos': 4, - 'catalog.rsid': 'rs4', - 'catalog.trait': 'graphomania', + 'assoc:chromosome': 1, + 'assoc:position': 4, + 'catalog:chrom': 1, + 'catalog:log_pvalue': 1.41, + 'catalog:pos': 4, + 'catalog:rsid': 'rs4', + 'catalog:trait': 'graphomania', 'n_catalog_matches': 2, }, { - 'assoc.chromosome': 1, - 'assoc.position': 6, - 'catalog.chrom': 1, - 'catalog.log_pvalue': 1.61, - 'catalog.pos': 6, - 'catalog.rsid': 'rs6', - 'catalog.trait': 'arithomania', + 'assoc:chromosome': 1, + 'assoc:position': 6, + 'catalog:chrom': 1, + 'catalog:log_pvalue': 1.61, + 'catalog:pos': 6, + 'catalog:rsid': 'rs6', + 'catalog:trait': 'arithomania', 'n_catalog_matches': 2, }, ]); @@ -95,7 +95,7 @@ describe('Custom join operations', function () { it('gracefully handles no catalog entries in region', function () { const { func, assoc_data } = this; - const res = func(assoc_data, [], 'assoc.position', 'catalog.pos', 'catalog.log_pvalue'); + const res = func(assoc_data, [], 'assoc:position', 'catalog:pos', 'catalog:log_pvalue'); assert.deepEqual(res, assoc_data); }); }); diff --git a/test/unit/data/test_requester.js b/test/unit/data/test_requester.js index ee91bf7c..73be73cc 100644 --- a/test/unit/data/test_requester.js +++ b/test/unit/data/test_requester.js @@ -30,13 +30,13 @@ describe('Requester object defines and parses requests', function () { type: 'left_match', name: 'combined', requires: ['assoc', 'ld'], - params: ['assoc.variant', 'ld.variant'], + params: ['assoc:variant', 'ld:variant'], }]; const [entities, dependencies] = this._requester._config_to_sources(namespace_options, join_options); // Validate names of dependencies are correct - assert.deepEqual(dependencies, ['assoc', 'ld', 'combined(assoc, ld)'], 'Dependencies are resolved in expected order'); + assert.deepEqual(dependencies, ['assoc', 'ld(assoc)', 'combined(assoc, ld)'], 'Dependencies are resolved in expected order'); // Validate that correct dependencies were wired up assert.deepEqual([...entities.keys()], ['assoc', 'ld', 'combined']); @@ -49,8 +49,8 @@ describe('Requester object defines and parses requests', function () { it('provides developer friendly error messages', function () { // Test parse errors: namespaces malformed assert.throws(() => { - this._requester._config_to_sources({ 'not.allowed': 'whatever' }, {}); - }, /Invalid namespace name: 'not\.allowed'/); + this._requester._config_to_sources({ 'not:allowed': 'whatever' }, {}); + }, /Invalid namespace name: 'not:allowed'/); // Test duplicate namespace errors: assoc assert.throws(() => { diff --git a/test/unit/data/test_sources.js b/test/unit/data/test_sources.js index 7b29eae9..7dc8af39 100644 --- a/test/unit/data/test_sources.js +++ b/test/unit/data/test_sources.js @@ -18,7 +18,7 @@ describe('DataSources object', function() { .add('assoc', ['AssociationLZ', { url }]); const source = data_sources.get('assoc'); assert.instanceOf(source, AssociationLZ); - assert.equal(url, source.url); + assert.equal(url, source._url); }); it('warns when trying to create a source of unknown type', function () { @@ -46,8 +46,8 @@ describe('DataSources object', function() { .add('assoc', instance1) .add('assoc2', instance2); - assert.equal(data_sources.get('assoc').url, 1); - assert.equal(data_sources.get('assoc2').url, 2); + assert.equal(data_sources.get('assoc')._url, 1); + assert.equal(data_sources.get('assoc2')._url, 2); }); it('should ensure that all sources are aware of their namespace', function () { diff --git a/test/unit/ext/test_ext_intervals-track.js b/test/unit/ext/test_ext_intervals-track.js index 86da1cc2..d6aa9bfd 100644 --- a/test/unit/ext/test_ext_intervals-track.js +++ b/test/unit/ext/test_ext_intervals-track.js @@ -131,9 +131,11 @@ describe('Interval annotation track', function () { beforeEach(function() { this.plot = null; const data_sources = new DataSources() - .add('intervals', ['StaticJSON', [ - { start: 100, end: 200, state_id: 'thing1', state_name: 'redfish', itemRgb: '255,0,0' }, - ]]); + .add('intervals', ['StaticJSON', { + data: [ + { start: 100, end: 200, state_id: 'thing1', state_name: 'redfish', itemRgb: '255,0,0' }, + ], + }]); const layout = { panels: [ LAYOUTS.get('panel', 'intervals') ], diff --git a/test/unit/helpers/test_display.js b/test/unit/helpers/test_display.js index 9abded34..a38bde36 100644 --- a/test/unit/helpers/test_display.js +++ b/test/unit/helpers/test_display.js @@ -170,12 +170,12 @@ describe('Display and parsing helpers', function () { assert.equal(parseFields(html, data), expected_value); const data2 = { 'fieldA': '', - 'fieldB': '', + 'foo:fieldB': '', }; - const html2 = '{{#if fieldA}}A1
{{/if}}' - + '{{#if fieldA|derp}}A2
{{/if}}' - + '{{#if foo:fieldB}}B1
{{/if}}' - + '{{#if foo:fieldB|derp}}B2
{{/if}}'; + const html2 = `{{#if fieldA}}A1
{{/if}}\ +{{#if fieldA|derp}}A2
{{/if}}\ +{{#if foo:fieldB}}B1
{{/if}}\ +{{#if foo:fieldB|derp}}B2
{{/if}}`; const expected_value2 = 'A2
B2
'; assert.equal(parseFields(html2, data2), expected_value2); }); diff --git a/test/unit/test_layouts.js b/test/unit/test_layouts.js index 774ea1aa..d94009e0 100644 --- a/test/unit/test_layouts.js +++ b/test/unit/test_layouts.js @@ -1,11 +1,11 @@ import { assert } from 'chai'; -import registry, {_LayoutRegistry} from '../../esm/registry/layouts'; +import LAYOUTS, {_LayoutRegistry} from '../../esm/registry/layouts'; import {deepCopy, merge} from '../../esm/helpers/layouts'; describe('_LayoutRegistry', function() { describe('Provides a method to list current layouts by type', function() { it ('No argument: returns an object, keys are layout types and values are arrays of layout names', function() { - const list = registry.list(); + const list = LAYOUTS.list(); assert.isObject(list); assert.hasAllKeys(list, ['plot', 'panel', 'data_layer', 'toolbar', 'toolbar_widgets', 'tooltip']); Object.values(list).forEach((listing) => { @@ -14,8 +14,8 @@ describe('_LayoutRegistry', function() { }); it ('Passed a valid type: returns array of layout names matching that type', function() { - const all = registry.list(); - const just_plots = registry.list('plot'); + const all = LAYOUTS.list(); + const just_plots = LAYOUTS.list('plot'); assert.isArray(just_plots); assert.deepEqual(just_plots, all.plot); @@ -25,13 +25,13 @@ describe('_LayoutRegistry', function() { describe('Provides a method to add new layouts', function() { it ('Requires arguments as (string, string, object) or throws an exception', function() { assert.throws(() => { - registry.add(); + LAYOUTS.add(); }, /must all/); assert.throws(() => { - registry.add('type_only'); + LAYOUTS.add('type_only'); }, /must all/, 'Only type provided'); assert.throws(() => { - registry.add('type', 'name'); + LAYOUTS.add('type', 'name'); }, /must all/, 'Type and name provided, but no item'); }); @@ -52,16 +52,16 @@ describe('_LayoutRegistry', function() { describe('Provides a method to get layout objects', function() { it('Must specify both type and name of the layout desired', function() { assert.throws(() => { - registry.get('plot'); + LAYOUTS.get('plot'); }, /Must specify/, 'Only type specified'); assert.throws(() => { - registry.get('plot', 'nonexistent'); + LAYOUTS.get('plot', 'nonexistent'); }, /not found/, 'Unrecognized type specified'); }); it('Returns layout object when type and name match', function() { - const result = registry.get('panel', 'association'); + const result = LAYOUTS.get('panel', 'association'); assert.equal(result.id, 'association'); }); @@ -382,11 +382,11 @@ describe('Layout helpers', function () { describe('Provides a method to query specific attributes', function () { it('can query a set of values based on a jsonpath selector', function () { - const base = registry.get('plot', 'standard_association'); + const base = LAYOUTS.get('plot', 'standard_association'); base.extra_field = false; const scenarios = [ - ['predicate_filters retrieve only list items where a specific condition is met', '$..color[?(@.scale_function === "if")].field', ['ld:isrefvar']], + ['predicate_filters retrieve only list items where a specific condition is met', '$..color[?(@.scale_function === "if")].field', ['lz_is_ld_refvar']], ['retrieves a list of scale function names', 'panels[?(@.tag === "association")]..scale_function', [ 'if', 'if', 'if', 'numerical_bin' ]], ['fetches dotted field path - one specific axis label', 'panels[?(@.tag === "association")].axes.x.label', [ 'Chromosome {{chr}} (Mb)' ]], ['is able to query and return falsy values', '$.extra_field', [false]], @@ -394,13 +394,13 @@ describe('Layout helpers', function () { ]; for (let [label, selector, expected] of scenarios) { - assert.sameDeepMembers(registry.query_attrs(base, selector), expected, `Scenario '${label}' passed`); + assert.sameDeepMembers(LAYOUTS.query_attrs(base, selector), expected, `Scenario '${label}' passed`); } }); it('can mutate a set of values based on a jsonpath selector', function () { - const base_panel = registry.get('panel', 'association'); - const base_layer = registry.get('data_layer', 'association_pvalues'); + const base_panel = LAYOUTS.get('panel', 'association'); + const base_layer = LAYOUTS.get('data_layer', 'association_pvalues'); const scenarios = [ ['set single value to a constant', '$.panels[?(@.tag === "association")].id', 'one', ['one']], @@ -413,9 +413,9 @@ describe('Layout helpers', function () { ]; for (let [label, selector, mutator, expected] of scenarios) { - const base = registry.get('plot', 'standard_association'); + const base = LAYOUTS.get('plot', 'standard_association'); base.fake_field = false; - assert.deepEqual(registry.mutate_attrs(base, selector, mutator), expected, `Scenario '${label}' passed`); + assert.deepEqual(LAYOUTS.mutate_attrs(base, selector, mutator), expected, `Scenario '${label}' passed`); } }); }); From b0b40a2db0a409ab57d831bedfbfbf9b6523b798 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 8 Sep 2021 14:13:35 -0400 Subject: [PATCH 012/100] Minor tidying and internal cleanup --- esm/components/data_layer/base.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 484328d1..17cec7f5 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -293,7 +293,7 @@ class BaseDataLayer { }; } - /****** Public interface: methods for external manipulation */ + /****** Public interface: methods for manipulating the layer from other parts of LZ */ /** * @public @@ -308,9 +308,11 @@ class BaseDataLayer { * @returns {BaseDataLayer} */ moveForward() { - if (this.parent.data_layer_ids_by_z_index[this.layout.z_index + 1]) { - this.parent.data_layer_ids_by_z_index[this.layout.z_index] = this.parent.data_layer_ids_by_z_index[this.layout.z_index + 1]; - this.parent.data_layer_ids_by_z_index[this.layout.z_index + 1] = this.id; + const layer_order = this.parent.data_layer_ids_by_z_index; + const current_index = this.layout.z_index; + if (layer_order[current_index + 1]) { + layer_order[current_index] = layer_order[current_index + 1]; + layer_order[current_index + 1] = this.id; this.parent.resortDataLayers(); } return this; @@ -322,9 +324,11 @@ class BaseDataLayer { * @returns {BaseDataLayer} */ moveBack() { - if (this.parent.data_layer_ids_by_z_index[this.layout.z_index - 1]) { - this.parent.data_layer_ids_by_z_index[this.layout.z_index] = this.parent.data_layer_ids_by_z_index[this.layout.z_index - 1]; - this.parent.data_layer_ids_by_z_index[this.layout.z_index - 1] = this.id; + const layer_order = this.parent.data_layer_ids_by_z_index; + const current_index = this.layout.z_index; + if (layer_order[current_index - 1]) { + layer_order[current_index] = layer_order[current_index - 1]; + layer_order[current_index - 1] = this.id; this.parent.resortDataLayers(); } return this; @@ -410,11 +414,11 @@ class BaseDataLayer { } /** + * Abstract method. It should be overridden by data layers that implement separate status + * nodes, such as genes or intervals. * Fetch an ID that may bind a data element to a separate visual node for displaying status - * Examples of this might be separate visual nodes to show select/highlight statuses, or - * even a common/shared node to show status across many elements in a set. - * Abstract method. It should be overridden by data layers that implement seperate status - * nodes specifically to the use case of the data layer type. + * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or + * a group of unrelated intervals (all markings grouped within a category). * @private * @param {String|Object} element * @returns {String|null} @@ -559,7 +563,6 @@ class BaseDataLayer { * @param {('x'|'y')} dimension */ getAxisExtent (dimension) { - if (!['x', 'y'].includes(dimension)) { throw new Error('Invalid dimension identifier'); } @@ -619,7 +622,6 @@ class BaseDataLayer { // No conditions met for generating a valid extent, return an empty array return []; - } /** From 955fc9f414f18ba70521bb61e1d5e35516a80ea4 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 8 Sep 2021 21:14:29 -0400 Subject: [PATCH 013/100] Remove unused `toHTML` and `deselect` data methods --- esm/components/data_layer/base.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 17cec7f5..b44a28bf 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -467,17 +467,9 @@ class BaseDataLayer { // When this layer receives data, mark whether points match (via a synthetic boolean field) // Any field-based layout directives (color, size, shape) can then be used to control display if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) { - item.lz_is_match = (match_function(field_resolver.resolve(item), broadcast_value)); + item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value); } - item.toHTML = () => { - const id_field = this.layout.id_field || 'id'; - let html = ''; - if (item[id_field]) { - html = item[id_field].toString(); - } - return html; - }; // Helper methods - return a reference to various plot levels. Useful for interactive tooltips. item.getDataLayer = () => this; item.getPanel = () => this.parent || null; @@ -486,11 +478,6 @@ class BaseDataLayer { const panel = this.parent; return panel ? panel.parent : null; }; - // deselect() method - shortcut method to deselect the element - item.deselect = () => { - const data_layer = this.getDataLayer(); - data_layer.unselectElement(this); // dynamically generated method name. It exists, honest. - }; }); this.applyCustomDataMethods(); return this; From f3487daa8aed029190b6ebbe3dc0670505d3d7d2 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 8 Sep 2021 22:32:24 -0400 Subject: [PATCH 014/100] Allow layers to infer fields contract, and soft-warn if a layer does not receive all the fields it expects from external providers --- esm/components/data_layer/base.js | 34 ++++++++++++- esm/components/data_layer/line.js | 4 -- esm/helpers/layouts.js | 46 +++++++++++++++++- test/unit/components/test_datalayer.js | 45 +++++++++++++++++- test/unit/helpers/test_layouts.js | 66 +++++++++++++++++++++++++- 5 files changed, 187 insertions(+), 8 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index b44a28bf..61c65c75 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -13,7 +13,7 @@ import * as d3 from 'd3'; import {STATUSES} from '../constants'; import Field from '../../data/field'; import {parseFields} from '../../helpers/display'; -import {deepCopy, merge} from '../../helpers/layouts'; +import {deepCopy, findFields, merge} from '../../helpers/layouts'; import MATCHERS from '../../registry/matchers'; import SCALABLE from '../../registry/scalable'; @@ -291,6 +291,11 @@ class BaseDataLayer { 'faded': false, 'hidden': false, }; + + // On first load, track all data behaviors and options + this._data_contract = new Set(); // List of all fields requested by the layout + + this.mutateLayout(); } /****** Public interface: methods for manipulating the layer from other parts of LZ */ @@ -367,6 +372,19 @@ class BaseDataLayer { this._filter_func = func; } + /** + * A list of operations that should be run when the layout is mutated + * Typically, these are things done once when a layout is first specified, that would not automatically + * update when the layout was changed. + */ + mutateLayout() { + // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract. + const { namespace } = this.layout; + if (namespace) { + this._data_contract = findFields(this.layout, Object.keys(namespace)); + } + } + /********** Protected methods: useful in subclasses to manipulate data layer behaviors */ /** * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other @@ -461,6 +479,20 @@ class BaseDataLayer { const broadcast_value = this.parent_plot.state.lz_match_value; // Match functions are allowed to use transform syntax on field values, but not (yet) UI "annotations" const field_resolver = field_to_match ? new Field(field_to_match) : null; + + if (this.data.length) { + // Does a sample of the data satisfy the fields expected by the layout? + const first_item_keys = new Set(Object.keys(this.data[0])); + // Set diff: contract - first_item_keys + let _difference = new Set(this._data_contract); + for (let elem of first_item_keys) { + _difference.delete(elem); + } + if (_difference.size) { + console.warn(`Data layer '${this.getBaseId()}' did not receive all expected fields based on first element of retrieved data. Missing fields are: ${[..._difference]}`); + } + } + this.data.forEach((item, i) => { // Basic toHTML() method - return the stringified value in the id_field, if defined. diff --git a/esm/components/data_layer/line.js b/esm/components/data_layer/line.js index c6189f14..6c05198b 100644 --- a/esm/components/data_layer/line.js +++ b/esm/components/data_layer/line.js @@ -178,10 +178,6 @@ class OrthogonalLine extends BaseDataLayer { layout.orientation = 'horizontal'; } super(...arguments); - - // Vars for storing the data generated line - /** @member {Array} */ - this.data = []; } getElementId(element) { diff --git a/esm/helpers/layouts.js b/esm/helpers/layouts.js index 3ad87165..279211eb 100644 --- a/esm/helpers/layouts.js +++ b/esm/helpers/layouts.js @@ -139,6 +139,50 @@ function nameToSymbol(shape) { return d3[factory_name] || null; } + +/** + * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided + * data adapters will actually give all the information required to draw the plot. + * @param {Object} layout + * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields, + * and random sentences that match an arbitrary pattern. + * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time + * @return {Set} + */ +function findFields(layout, prefixes, field_finder = null) { + if (!field_finder) { + if (!prefixes.length) { + // A layer that doesn't ask for external data does not need to check if the provider returns expected fields + return []; + } + const all_ns = prefixes.join('|'); + + // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`. + // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches + field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\w+)`, 'g'); + } + + const fields = new Set(); + + for (const value of Object.values(layout)) { + const key_type = typeof value; + let matches; + if (key_type === 'string') { + matches = [...value.matchAll(field_finder)].map((m) => m[1]); + } else if (value !== null && key_type === 'object') { + matches = findFields(value, prefixes, field_finder); + } else { + // Only look for field names in strings or compound values + continue; + } + for (let m of matches) { + fields.add(m); + } + } + return fields; +} + + /** * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string), * replaces *exact match* @@ -222,4 +266,4 @@ function query_attrs(layout, selector) { return query(layout, selector); } -export { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, renameField }; +export { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField }; diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index 6428fe82..46d351b3 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -12,6 +12,21 @@ import DataSources from '../../../esm/data'; Test composition of the LocusZoom.Panel object and its base classes */ describe('LocusZoom.DataLayer', function () { + describe('data contract parsing and validation', function () { + after(function() { + sinon.restore(); + }); + + it('warns if the data received does not match the inferred fields contract', function () { + let spy = sinon.spy(console, 'warn'); + const layer = new BaseDataLayer({}); + layer.parent_plot = { state: {} }; + layer._data_contract = ['assoc:variant', 'assoc:rsid']; + layer.data = [{ 'assoc:variant': '1:23_A/B', 'assoc:position': 23 }]; + layer.applyDataMethods(); + assert.ok(spy.calledOnce, 'Console.warn was called with data contract errors'); + }); + }); describe('Z-index sorting', function () { beforeEach(function () { @@ -33,10 +48,12 @@ describe('LocusZoom.DataLayer', function () { d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', null, layout); }); + afterEach(function () { d3.select('#plot').remove(); this.plot = null; }); + it('should have a chainable method for moving layers up that stops at the top', function () { assert.deepEqual(this.plot.panels.panel0.data_layer_ids_by_z_index, ['layerA', 'layerB', 'layerC', 'layerD']); @@ -61,6 +78,7 @@ describe('LocusZoom.DataLayer', function () { assert.equal(this.plot.panels.panel0.data_layers.layerC.layout.z_index, 1); assert.equal(this.plot.panels.panel0.data_layers.layerD.layout.z_index, 2); }); + it('should have a chainable method for moving layers down that stops at the bottom', function () { assert.deepEqual(this.plot.panels.panel0.data_layer_ids_by_z_index, ['layerA', 'layerB', 'layerC', 'layerD']); @@ -97,6 +115,7 @@ describe('LocusZoom.DataLayer', function () { assert.equal(datalayer.resolveScalableParameter(this.layout.scale, {}), 17); assert.equal(datalayer.resolveScalableParameter(this.layout.scale, { foo: 'bar' }), 17); }); + it('executes a scale function for the data provided', function () { const datalayer = new BaseDataLayer({ id: 'test' }); const layout = { @@ -113,6 +132,7 @@ describe('LocusZoom.DataLayer', function () { assert.equal(datalayer.resolveScalableParameter(layout.scale, { test: 'manatee' }), null); assert.equal(datalayer.resolveScalableParameter(layout.scale, {}), null); }); + it('can operate in a state-aware manner based on index in data[]', function () { SCALABLE.add('fake', (parameters, input, index) => index); const datalayer = new BaseDataLayer({ id: 'test' }); @@ -124,6 +144,7 @@ describe('LocusZoom.DataLayer', function () { // Clean up/ deregister scale function when done SCALABLE.remove('fake'); }); + it('supports operating on an entire data element in the absence of a specified field', function () { SCALABLE.add('test_effect_direction', function (parameters, input) { if (typeof input == 'undefined') { @@ -155,6 +176,7 @@ describe('LocusZoom.DataLayer', function () { // Clean up/ deregister scale function when done SCALABLE.remove('test_effect_direction'); }); + it('iterates over an array of options until exhausted or a non-null value is found', function () { const datalayer = new BaseDataLayer({ id: 'test' }); const layout = { @@ -183,6 +205,7 @@ describe('LocusZoom.DataLayer', function () { assert.equal(datalayer.resolveScalableParameter(layout.scale, { test: 'witch' }), 'munchkin'); assert.equal(datalayer.resolveScalableParameter(layout.scale, {}), 'munchkin'); }); + it('can resolve based on an annotation field, even when no point data field by that name is present', function () { const layout = { id: 'somelayer', @@ -217,6 +240,7 @@ describe('LocusZoom.DataLayer', function () { datalayer.getAxisExtent('y1'); }); }); + it('generates an accurate extent array for arbitrary data sets', function () { const layout = { id: 'test', @@ -247,6 +271,7 @@ describe('LocusZoom.DataLayer', function () { ]; assert.deepEqual(datalayer.getAxisExtent('x'), [undefined, undefined]); }); + it('applies upper and lower buffers to extents as defined in the layout', function () { let layout = { id: 'test', @@ -286,6 +311,7 @@ describe('LocusZoom.DataLayer', function () { ]; assert.deepEqual(datalayer.getAxisExtent('x'), [-95, 412]); }); + it('applies a minimum extent as defined in the layout', function () { let layout = { id: 'test', @@ -329,6 +355,7 @@ describe('LocusZoom.DataLayer', function () { assert.deepEqual(datalayer.getAxisExtent('x'), [-1.48, 10.74], 'Padding is enforced on both sides when data is close to both boundaries'); }); + it('applies hard floor and ceiling as defined in the layout', function () { let layout = { id: 'test', @@ -375,7 +402,6 @@ describe('LocusZoom.DataLayer', function () { ]; assert.deepEqual(datalayer.getAxisExtent('x'), [4, 6]); }); - }); describe('Layout Parameters', function () { @@ -391,10 +417,12 @@ describe('LocusZoom.DataLayer', function () { }; d3.select('body').append('div').attr('id', 'plot'); }); + afterEach(function () { d3.select('#plot').remove(); delete this.plot; }); + it('should allow for explicitly setting data layer z_index', function () { this.layout.panels[0].data_layers = [ { id: 'd1', type: 'line', z_index: 1 }, @@ -405,6 +433,7 @@ describe('LocusZoom.DataLayer', function () { assert.equal(this.plot.panels.p1.data_layers.d1.layout.z_index, 1); assert.equal(this.plot.panels.p1.data_layers.d2.layout.z_index, 0); }); + it('should allow for explicitly setting data layer z_index with a negative value', function () { this.layout.panels[0].data_layers = [ { id: 'd1', type: 'line' }, @@ -446,10 +475,12 @@ describe('LocusZoom.DataLayer', function () { d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', data_sources, layout); }); + afterEach(function () { d3.select('#plot').remove(); delete this.plot; }); + it('should allow for highlighting and unhighlighting a single element', function () { return this.plot.lzd.getData({}, { d: 'd' }, []) .then(() => { @@ -483,6 +514,7 @@ describe('LocusZoom.DataLayer', function () { assert.equal(highlight_flags.size, 0); }); }); + it('should allow for highlighting and unhighlighting all elements', function () { return this.plot.lzd.getData({}, { d: 'd' }, []) .then(() => { @@ -531,10 +563,12 @@ describe('LocusZoom.DataLayer', function () { d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', data_sources, layout); }); + afterEach(function () { d3.select('#plot').remove(); delete this.plot; }); + it('should allow for selecting and unselecting a single element', function () { return this.plot.lzd.getData({}, { d: 'd' }, []) .then(() => { @@ -568,6 +602,7 @@ describe('LocusZoom.DataLayer', function () { assert.equal(selected_flags.size, 0); }); }); + it('should allow for selecting and unselecting all elements', function () { return this.plot.lzd.getData({}, { d: 'd' }, []) .then(() => { @@ -616,10 +651,12 @@ describe('LocusZoom.DataLayer', function () { d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', null, this.layout); }); + afterEach(function () { d3.select('#plot').remove(); delete this.plot; }); + it('should allow for creating and destroying tool tips', function () { this.plot.panels.p.data_layers.d.data = [{ id: 'a' }, { id: 'b' }, { id: 'c' }]; this.plot.panels.p.data_layers.d.positionTooltip = function () { @@ -640,6 +677,7 @@ describe('LocusZoom.DataLayer', function () { assert.equal(typeof this.plot.panels.p.data_layers.d.tooltips[a_id], 'undefined'); assert.equal(d3.select(a_id_q).empty(), true); }); + it('should allow for showing or hiding a tool tip based on layout directives and element status', function () { this.plot.panels.p.data_layers.d.data = [{ id: 'a' }, { id: 'b' }, { id: 'c' }]; this.plot.panels.p.data_layers.d.positionTooltip = function () { @@ -677,6 +715,7 @@ describe('LocusZoom.DataLayer', function () { d.unselectElement(b); assert.isUndefined(d.tooltips[b_id]); }); + it('should allow tooltip open/close state to be tracked separately from element selection', function () { // Regression test for zombie tooltips returning after re-render const layer = this.plot.panels.p.data_layers.d; @@ -746,6 +785,7 @@ describe('LocusZoom.DataLayer', function () { d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', data_sources, layout); }); + it('can link to an external website', function () { // NOTE: Not all variants of this behavior are tested. This only tests opening links in another window. // This is because JSDom sometimes has issues mocking window.location. @@ -762,6 +802,7 @@ describe('LocusZoom.DataLayer', function () { assert.ok(openStub.calledWith('https://dev.example/a', '_blank'), 'The URL can incorporate parameters from the specified data element'); }); }); + it('applies status-based styles when an item receives mouse events', function () { // Since sequence is important, this test exercises multiple scenarios in a specific order return this.plot.applyState().then(() => { @@ -786,6 +827,7 @@ describe('LocusZoom.DataLayer', function () { assert.notOk(second.node().classList.contains('lz-data_layer-scatter-highlighted'), 'Style is removed on mouseout'); }); }); + it('recognizes keyboard modifiers as distinct events', function () { return this.plot.applyState().then(() => { const openStub = sinon.stub(window, 'open'); @@ -800,6 +842,7 @@ describe('LocusZoom.DataLayer', function () { assert.ok(datapoint.node().classList.contains('lz-data_layer-scatter-selected'), 'Style is applied appropriately'); }); }); + afterEach(function () { sinon.restore(); }); diff --git a/test/unit/helpers/test_layouts.js b/test/unit/helpers/test_layouts.js index 727cbc02..8ae1c93e 100644 --- a/test/unit/helpers/test_layouts.js +++ b/test/unit/helpers/test_layouts.js @@ -1,9 +1,73 @@ import {assert} from 'chai'; import sinon from 'sinon'; -import {renameField} from '../../../esm/helpers/layouts'; +import {findFields, renameField} from '../../../esm/helpers/layouts'; describe('Layout helper functions', function () { + describe('findFields', function () { + it('does not try to find requested fields if there are no namespaces declared', function () { + const layout = { + x_field: 'assoc:position', + y_field: 'ld:correlation', + color: 'red', + }; + + const result = [...findFields(layout, [])]; + assert.equal(result.length, 0, 'No fields looked for or found'); + }); + + it('finds simple primitive values', function () { + const namespaces = ['assoc', 'ld']; + const layout = { + x_field: 'assoc:position', + y_field: 'ld:correlation', + label_field_with_filter: 'assoc:variant|htmlescape', + redundant_field: 'assoc:variant', + color: 'red', + spurious: 'X:2500_A/C', + }; + const result = [...findFields(layout, namespaces)]; + assert.sameMembers(result, ['assoc:position', 'assoc:variant', 'ld:correlation'], 'Finds all unique valid field names (and strips filter usages)'); + }); + + it('finds values inside template syntax', function () { + const namespaces = ['assoc']; + const layout = { + y_field: 'ld:correlation', // Not listed in namespace = not in fields contract + text: '{{assoc:nearest_gene}} - {{#if assoc:rsid}} Date: Wed, 8 Sep 2021 23:02:26 -0400 Subject: [PATCH 015/100] Remove fields contract from adapter class, because a layer doesn't know / care what class was used to get its data (the check-the-payload approach will be a more reliable source of truth in practice) --- esm/data/adapters.js | 31 +++---------------------------- test/unit/data/test_adapters.js | 8 -------- 2 files changed, 3 insertions(+), 36 deletions(-) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index cce1d890..eb42dbed 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -63,15 +63,13 @@ class BaseLZAdapter extends BaseUrlAdapter { constructor(config = {}) { super(config); - this._validate_fields = true; // Prefix the namespace for this source to all fieldnames: id -> assoc.id // This is useful for almost all layers because the layout object says where to find every field, exactly. // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear // in the response. (gene_name instead of genes.gene_name) - const {prefix_namespace, limit_fields} = config; + const {prefix_namespace} = config; this._prefix_namespace = (typeof prefix_namespace === 'boolean') ? prefix_namespace : true; - this._limit_fields = limit_fields; } _getCacheKey(options) { @@ -107,10 +105,8 @@ class BaseLZAdapter extends BaseUrlAdapter { return records.map((row) => { return Object.entries(row).reduce( (acc, [label, value]) => { - if (!this._limit_fields || this._fields_contract.has(label)) { - // Rename API fields to format `namespace:fieldname` - acc[`${options._provider_name}:${label}`] = value; - } + // Rename API fields to format `namespace:fieldname` + acc[`${options._provider_name}:${label}`] = value; return acc; }, {} @@ -197,10 +193,6 @@ class BaseUMAdapter extends BaseLZAdapter { class AssociationLZ extends BaseUMAdapter { constructor(config = {}) { - // Minimum adapter contract hard-codes fields contract based on UM PortalDev API + default assoc plot layout - // For layers that require more functionality, pass extra_fields to source options - config.fields = ['variant', 'position', 'log_pvalue', 'ref_allele']; - super(config); // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files @@ -238,10 +230,6 @@ class GwasCatalogLZ extends BaseUMAdapter { * @param {Number} [config.params.source] The ID of the chosen catalog. Most usages should omit this parameter and * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37. */ - constructor(config = {}) { - config.fields = ['rsid', 'trait', 'log_pvalue']; - super(config); - } /** * Add query parameters to the URL to construct a query for the specified region @@ -278,7 +266,6 @@ class GeneLZ extends BaseUMAdapter { // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given. // We will avoid transforming or modifying the payload. - this._validate_fields = false; this._prefix_namespace = false; } @@ -319,7 +306,6 @@ class GeneConstraintLZ extends BaseLZAdapter { */ constructor(config = {}) { super(config); - this._validate_fields = false; this._prefix_namespace = false; } @@ -384,12 +370,6 @@ class GeneConstraintLZ extends BaseLZAdapter { class LDServer extends BaseUMAdapter { - constructor(config = {}) { - // item1 = refvar, item2 = othervar - config.fields = ['chromosome2', 'position2', 'variant2', 'correlation']; - super(config); - } - __find_ld_refvar(state, assoc_data) { const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant'); const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue'); @@ -537,11 +517,6 @@ class LDServer extends BaseUMAdapter { * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37. */ class RecombLZ extends BaseUMAdapter { - constructor(config = {}) { - config.fields = ['position', 'recomb_rate']; - super(config); - } - /** * Add query parameters to the URL to construct a query for the specified region */ diff --git a/test/unit/data/test_adapters.js b/test/unit/data/test_adapters.js index 10904a50..ce0bc71b 100644 --- a/test/unit/data/test_adapters.js +++ b/test/unit/data/test_adapters.js @@ -65,14 +65,6 @@ describe('Data adapters', function () { ).then((result) => assert.deepEqual(result, [{'sometest:a': 1, 'sometest:b': 2}])); }); - it('can restrict the list of returned fields to those in the fields contract', function () { - const source = new BaseTestClass({ limit_fields: true, fields: ['b'] }); - return source.getData({ - _provider_name: 'sometest', - _test_data: [{ a: 1, b: 2 }]} - ).then((result) => assert.deepEqual(result, [{ 'sometest:b': 2}])); - }); - it('has a helper to locate dependent fields that were already namespaced', function () { const source = new BaseTestClass({}); const match = source._findPrefixedKey({'sometest:aaa': 1, 'sometest:a': 2, 'sometest:aa': 3}, 'a'); From ef5e41c5cfe97a9bb4c0398612b89005850a653a Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 9 Sep 2021 14:59:54 -0400 Subject: [PATCH 016/100] Improve fields validation to reflect join behaviors --- esm/components/data_layer/base.js | 24 +++++++++++++++--------- test/unit/components/test_datalayer.js | 24 +++++++++++++++++++----- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 61c65c75..002f3d88 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -480,16 +480,22 @@ class BaseDataLayer { // Match functions are allowed to use transform syntax on field values, but not (yet) UI "annotations" const field_resolver = field_to_match ? new Field(field_to_match) : null; - if (this.data.length) { - // Does a sample of the data satisfy the fields expected by the layout? - const first_item_keys = new Set(Object.keys(this.data[0])); - // Set diff: contract - first_item_keys - let _difference = new Set(this._data_contract); - for (let elem of first_item_keys) { - _difference.delete(elem); + // Does the data from the API satisfy the list of fields expected by this layout? + // Not every record will have every possible field (example: left joins like assoc + ld). The check is "did + // we see this field at least once in any record at all". + if (this.data.length && this._data_contract.size) { + const fields_unseen = new Set(this._data_contract); + for (let record of this.data) { + Object.keys(record).forEach((field) => fields_unseen.delete(field)); + if (!fields_unseen.size) { + // Once every requested field has been seen in at least one record, no need to look at more records + break; + } } - if (_difference.size) { - console.warn(`Data layer '${this.getBaseId()}' did not receive all expected fields based on first element of retrieved data. Missing fields are: ${[..._difference]}`); + if (fields_unseen.size) { + // Current implementation is a soft warning, so that certain "incremental enhancement" features (like rsIDs in tooltips) can fail gracefully is the API does not provide the requested info + // This basically exists because of the `{{#if fieldname}}` statement in template string syntax. + console.warn(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}`); } } diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index 46d351b3..b6e970be 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -13,18 +13,32 @@ import DataSources from '../../../esm/data'; */ describe('LocusZoom.DataLayer', function () { describe('data contract parsing and validation', function () { + beforeEach(function () { + const layer = this.layer = new BaseDataLayer({ id: 'something' }); + layer.parent_plot = { state: {} }; + layer._data_contract = new Set(['assoc:variant', 'assoc:rsid']); + }); + after(function() { sinon.restore(); }); it('warns if the data received does not match the inferred fields contract', function () { let spy = sinon.spy(console, 'warn'); - const layer = new BaseDataLayer({}); - layer.parent_plot = { state: {} }; - layer._data_contract = ['assoc:variant', 'assoc:rsid']; - layer.data = [{ 'assoc:variant': '1:23_A/B', 'assoc:position': 23 }]; - layer.applyDataMethods(); + this.layer.data = [{ 'assoc:variant': '1:23_A/B', 'assoc:position': 23 }]; + this.layer.applyDataMethods(); assert.ok(spy.calledOnce, 'Console.warn was called with data contract errors'); + assert.ok(spy.firstCall.args[0].match(/Missing fields are: assoc:rsid/), 'Developer message identifies the missing fields'); + }); + + it('will treat the fields contract as satisfied if the field is in at least one record of the response', function () { + let spy = sinon.spy(console, 'warn'); + this.layer.data = [ + { 'assoc:variant': '1:23_A/B', 'assoc:position': 23 }, + { 'assoc:variant': '1:24_A/B', 'assoc:position': 24, 'assoc:rsid': 'rsYuppers' }, + ]; + this.layer.applyDataMethods(); + assert.ok(spy.notCalled, 'Console.warn was not called with contract errors'); }); }); From 34963e63a3871b0ff3ec0b99c74ad4fdecf83337 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Fri, 10 Sep 2021 17:37:49 -0400 Subject: [PATCH 017/100] Minor code cleanup: introduce variables for repeated lookups [ci skip] --- esm/components/data_layer/line.js | 1 + esm/components/panel.js | 103 +++++++++++++------------ esm/components/plot.js | 24 +++--- esm/helpers/layouts.js | 2 + package-lock.json | 5 ++ package.json | 1 + test/unit/components/test_datalayer.js | 2 +- test/unit/components/test_panel.js | 25 +++--- test/unit/components/test_plot.js | 43 +++++++++-- test/unit/test_layouts.js | 72 +++++------------ 10 files changed, 143 insertions(+), 135 deletions(-) diff --git a/esm/components/data_layer/line.js b/esm/components/data_layer/line.js index 6c05198b..27b07096 100644 --- a/esm/components/data_layer/line.js +++ b/esm/components/data_layer/line.js @@ -17,6 +17,7 @@ const default_layout = { x_axis: { field: 'x' }, y_axis: { field: 'y', axis: 1 }, hitarea_width: 5, + tooltip: null, }; /********************* diff --git a/esm/components/panel.js b/esm/components/panel.js index 11609433..767f23ba 100644 --- a/esm/components/panel.js +++ b/esm/components/panel.js @@ -451,7 +451,7 @@ class Panel { throw new Error('Invalid data layer layout'); } if (typeof this.data_layers[layout.id] !== 'undefined') { - throw new Error(`Cannot create data_layer with id [${layout.id}]; data layer with that id already exists in the panel`); + throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`); } if (typeof layout.type !== 'string') { throw new Error('Invalid data layer type'); @@ -555,7 +555,6 @@ class Panel { * @returns {Panel} */ render() { - // Position the panel container this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`); @@ -564,6 +563,8 @@ class Panel { .attr('width', this.parent_plot.layout.width) .attr('height', this.layout.height); + const { cliparea } = this.layout; + // Set and position the inner border, style if necessary this.inner_border .attr('x', this.layout.margin.left) @@ -619,7 +620,7 @@ class Panel { ranges.x_shifted = [base_x_range.start, base_x_range.end]; } if (this.y1_extent) { - const base_y1_range = { start: this.layout.cliparea.height, end: 0 }; + const base_y1_range = { start: cliparea.height, end: 0 }; if (this.layout.axes.y1.range) { base_y1_range.start = this.layout.axes.y1.range.start || base_y1_range.start; base_y1_range.end = this.layout.axes.y1.range.end || base_y1_range.end; @@ -628,7 +629,7 @@ class Panel { ranges.y1_shifted = [base_y1_range.start, base_y1_range.end]; } if (this.y2_extent) { - const base_y2_range = { start: this.layout.cliparea.height, end: 0 }; + const base_y2_range = { start: cliparea.height, end: 0 }; if (this.layout.axes.y2.range) { base_y2_range.start = this.layout.axes.y2.range.start || base_y2_range.start; base_y2_range.end = this.layout.axes.y2.range.end || base_y2_range.end; @@ -638,12 +639,14 @@ class Panel { } // Shift ranges based on any drag or zoom interactions currently underway - if (this.parent.interaction.panel_id && (this.parent.interaction.panel_id === this.id || this.parent.interaction.linked_panel_ids.includes(this.id))) { + const interaction = this.parent.interaction; + const current_drag = interaction.dragging; + if (interaction.panel_id && (interaction.panel_id === this.id || interaction.linked_panel_ids.includes(this.id))) { let anchor, scalar = null; - if (this.parent.interaction.zooming && typeof this.x_scale == 'function') { + if (interaction.zooming && typeof this.x_scale == 'function') { const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]); const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0])); - let zoom_factor = this.parent.interaction.zooming.scale; + let zoom_factor = interaction.zooming.scale; const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor)); if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) { zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size); @@ -651,38 +654,38 @@ class Panel { zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size); } const new_extent_size = Math.floor(current_extent_size * zoom_factor); - anchor = this.parent.interaction.zooming.center - this.layout.margin.left - this.layout.origin.x; - const offset_ratio = anchor / this.layout.cliparea.width; + anchor = interaction.zooming.center - this.layout.margin.left - this.layout.origin.x; + const offset_ratio = anchor / cliparea.width; const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1); ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ]; - } else if (this.parent.interaction.dragging) { - switch (this.parent.interaction.dragging.method) { + } else if (current_drag) { + switch (current_drag.method) { case 'background': - ranges.x_shifted[0] = +this.parent.interaction.dragging.dragged_x; - ranges.x_shifted[1] = this.layout.cliparea.width + this.parent.interaction.dragging.dragged_x; + ranges.x_shifted[0] = +current_drag.dragged_x; + ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x; break; case 'x_tick': if (d3.event && d3.event.shiftKey) { - ranges.x_shifted[0] = +this.parent.interaction.dragging.dragged_x; - ranges.x_shifted[1] = this.layout.cliparea.width + this.parent.interaction.dragging.dragged_x; + ranges.x_shifted[0] = +current_drag.dragged_x; + ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x; } else { - anchor = this.parent.interaction.dragging.start_x - this.layout.margin.left - this.layout.origin.x; - scalar = constrain(anchor / (anchor + this.parent.interaction.dragging.dragged_x), 3); + anchor = current_drag.start_x - this.layout.margin.left - this.layout.origin.x; + scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3); ranges.x_shifted[0] = 0; - ranges.x_shifted[1] = Math.max(this.layout.cliparea.width * (1 / scalar), 1); + ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1); } break; case 'y1_tick': case 'y2_tick': { - const y_shifted = `y${this.parent.interaction.dragging.method[1]}_shifted`; + const y_shifted = `y${current_drag.method[1]}_shifted`; if (d3.event && d3.event.shiftKey) { - ranges[y_shifted][0] = this.layout.cliparea.height + this.parent.interaction.dragging.dragged_y; - ranges[y_shifted][1] = +this.parent.interaction.dragging.dragged_y; + ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y; + ranges[y_shifted][1] = +current_drag.dragged_y; } else { - anchor = this.layout.cliparea.height - (this.parent.interaction.dragging.start_y - this.layout.margin.top - this.layout.origin.y); - scalar = constrain(anchor / (anchor - this.parent.interaction.dragging.dragged_y), 3); - ranges[y_shifted][0] = this.layout.cliparea.height; - ranges[y_shifted][1] = this.layout.cliparea.height - (this.layout.cliparea.height * (1 / scalar)); + anchor = cliparea.height - (current_drag.start_y - this.layout.margin.top - this.layout.origin.y); + scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3); + ranges[y_shifted][0] = cliparea.height; + ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar)); } } } @@ -743,7 +746,7 @@ class Panel { }, }; this.render(); - this.parent.interaction.linked_panel_ids.forEach((panel_id) => { + interaction.linked_panel_ids.forEach((panel_id) => { this.parent.panels[panel_id].render(); }); if (this.zoom_timeout !== null) { @@ -858,13 +861,14 @@ class Panel { this.y2_range = [this.layout.cliparea.height, 0]; // Initialize panel axes - ['x', 'y1', 'y2'].forEach((axis) => { - if (!Object.keys(this.layout.axes[axis]).length || this.layout.axes[axis].render === false) { + ['x', 'y1', 'y2'].forEach((id) => { + const axis = this.layout.axes[id]; + if (!Object.keys(axis).length || axis.render === false) { // The default layout sets the axis to an empty object, so set its render boolean here - this.layout.axes[axis].render = false; + axis.render = false; } else { - this.layout.axes[axis].render = true; - this.layout.axes[axis].label = this.layout.axes[axis].label || null; + axis.render = true; + axis.label = axis.label || null; } }); @@ -945,36 +949,37 @@ class Panel { */ setMargin(top, right, bottom, left) { let extra; - if (!isNaN(top) && top >= 0) { - this.layout.margin.top = Math.max(Math.round(+top), 0); + const { cliparea, margin } = this.layout; + if (!isNaN(top) && top >= 0) { + margin.top = Math.max(Math.round(+top), 0); } if (!isNaN(right) && right >= 0) { - this.layout.margin.right = Math.max(Math.round(+right), 0); + margin.right = Math.max(Math.round(+right), 0); } if (!isNaN(bottom) && bottom >= 0) { - this.layout.margin.bottom = Math.max(Math.round(+bottom), 0); + margin.bottom = Math.max(Math.round(+bottom), 0); } if (!isNaN(left) && left >= 0) { - this.layout.margin.left = Math.max(Math.round(+left), 0); + margin.left = Math.max(Math.round(+left), 0); } // If the specified margins are greater than the available width, then shrink the margins. - if (this.layout.margin.top + this.layout.margin.bottom > this.layout.height) { - extra = Math.floor(((this.layout.margin.top + this.layout.margin.bottom) - this.layout.height) / 2); - this.layout.margin.top -= extra; - this.layout.margin.bottom -= extra; + if (margin.top + margin.bottom > this.layout.height) { + extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2); + margin.top -= extra; + margin.bottom -= extra; } - if (this.layout.margin.left + this.layout.margin.right > this.parent_plot.layout.width) { - extra = Math.floor(((this.layout.margin.left + this.layout.margin.right) - this.parent_plot.layout.width) / 2); - this.layout.margin.left -= extra; - this.layout.margin.right -= extra; + if (margin.left + margin.right > this.parent_plot.layout.width) { + extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2); + margin.left -= extra; + margin.right -= extra; } ['top', 'right', 'bottom', 'left'].forEach((m) => { - this.layout.margin[m] = Math.max(this.layout.margin[m], 0); + margin[m] = Math.max(margin[m], 0); }); - this.layout.cliparea.width = Math.max(this.parent_plot.layout.width - (this.layout.margin.left + this.layout.margin.right), 0); - this.layout.cliparea.height = Math.max(this.layout.height - (this.layout.margin.top + this.layout.margin.bottom), 0); - this.layout.cliparea.origin.x = this.layout.margin.left; - this.layout.cliparea.origin.y = this.layout.margin.top; + cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0); + cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0); + cliparea.origin.x = margin.left; + cliparea.origin.y = margin.top; if (this.initialized) { this.render(); diff --git a/esm/components/plot.js b/esm/components/plot.js index 90fdf522..b6d6bb38 100644 --- a/esm/components/plot.js +++ b/esm/components/plot.js @@ -1007,10 +1007,10 @@ class Plot { // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate // proportional heights for all panels with a null value from discretely set dimensions. // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width) - for (let id in this.panels) { - if (this.panels[id].layout.interaction.x_linked) { - x_linked_margins.left = Math.max(x_linked_margins.left, this.panels[id].layout.margin.left); - x_linked_margins.right = Math.max(x_linked_margins.right, this.panels[id].layout.margin.right); + for (let panel of Object.values(this.panels)) { + if (panel.layout.interaction.x_linked) { + x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left); + x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right); } } @@ -1019,15 +1019,16 @@ class Plot { let y_offset = 0; this.panel_ids_by_y_index.forEach((panel_id) => { const panel = this.panels[panel_id]; + const panel_layout = panel.layout; panel.setOrigin(0, y_offset); y_offset += this.panels[panel_id].layout.height; - if (panel.layout.interaction.x_linked) { - const delta = Math.max(x_linked_margins.left - panel.layout.margin.left, 0) - + Math.max(x_linked_margins.right - panel.layout.margin.right, 0); - panel.layout.width += delta; - panel.layout.margin.left = x_linked_margins.left; - panel.layout.margin.right = x_linked_margins.right; - panel.layout.cliparea.origin.x = x_linked_margins.left; + if (panel_layout.interaction.x_linked) { + const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0) + + Math.max(x_linked_margins.right - panel_layout.margin.right, 0); + panel_layout.width += delta; + panel_layout.margin.left = x_linked_margins.left; + panel_layout.margin.right = x_linked_margins.right; + panel_layout.cliparea.origin.x = x_linked_margins.left; } }); @@ -1054,7 +1055,6 @@ class Plot { * @returns {Plot} */ initialize() { - // Ensure proper responsive class is present on the containing node if called for if (this.layout.responsive_resize) { d3.select(this.container).classed('lz-container-responsive', true); diff --git a/esm/helpers/layouts.js b/esm/helpers/layouts.js index 279211eb..632cc099 100644 --- a/esm/helpers/layouts.js +++ b/esm/helpers/layouts.js @@ -118,6 +118,8 @@ function merge(custom_layout, default_layout) { } function deepCopy(item) { + // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future. + // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects return JSON.parse(JSON.stringify(item)); } diff --git a/package-lock.json b/package-lock.json index 3d62c4bf..3fc639de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2678,6 +2678,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "default-require-extensions": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", diff --git a/package.json b/package.json index 8205abd8..fe93b1f1 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "d3": "^5.16.0", + "deepmerge": "^4.2.2", "just-clone": "^3.2.1" }, "devDependencies": { diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index b6e970be..1d1f2b9b 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -19,7 +19,7 @@ describe('LocusZoom.DataLayer', function () { layer._data_contract = new Set(['assoc:variant', 'assoc:rsid']); }); - after(function() { + afterEach(function() { sinon.restore(); }); diff --git a/test/unit/components/test_panel.js b/test/unit/components/test_panel.js index 8e15d5d5..8e7473e5 100644 --- a/test/unit/components/test_panel.js +++ b/test/unit/components/test_panel.js @@ -193,20 +193,21 @@ describe('Panel', function() { }); it('should have a method for removing data layers by id', function() { - this.plot.panels.panel0.addDataLayer({ id: 'layerA', type: 'line' }); - this.plot.panels.panel0.addDataLayer({ id: 'layerB', type: 'line' }); - this.plot.panels.panel0.addDataLayer({ id: 'layerC', type: 'line' }); - const state_id = this.plot.panels.panel0.data_layers.layerB.state_id; - assert.equal(typeof this.plot.panels.panel0.data_layers.layerB, 'object'); + const panel0 = this.plot.panels.panel0; + const layerA = panel0.addDataLayer({ id: 'layerA', type: 'line' }); + const layerB = panel0.addDataLayer({ id: 'layerB', type: 'line' }); + const layerC = panel0.addDataLayer({ id: 'layerC', type: 'line' }); + const state_id = layerB.state_id; + assert.equal(typeof layerB, 'object'); assert.equal(typeof this.plot.state[state_id], 'object'); - this.plot.panels.panel0.removeDataLayer('layerB'); - assert.equal(typeof this.plot.panels.panel0.data_layers.layerB, 'undefined'); + panel0.removeDataLayer('layerB'); + assert.equal(typeof panel0.data_layers.layerB, 'undefined'); assert.equal(typeof this.plot.state[state_id], 'undefined'); - assert.equal(this.plot.panels.panel0.data_layers.layerA.layout_idx, 0); - assert.equal(this.plot.panels.panel0.data_layers.layerC.layout_idx, 1); - assert.equal(this.plot.panels.panel0.data_layers.layerA.layout.z_index, 0); - assert.equal(this.plot.panels.panel0.data_layers.layerC.layout.z_index, 1); - assert.deepEqual(this.plot.panels.panel0.data_layer_ids_by_z_index, ['layerA', 'layerC']); + assert.equal(layerA.layout_idx, 0); + assert.equal(layerC.layout_idx, 1); + assert.equal(layerA.layout.z_index, 0); + assert.equal(layerC.layout.z_index, 1); + assert.deepEqual(panel0.data_layer_ids_by_z_index, ['layerA', 'layerC']); }); }); diff --git a/test/unit/components/test_plot.js b/test/unit/components/test_plot.js index 604674f6..49fbc076 100644 --- a/test/unit/components/test_plot.js +++ b/test/unit/components/test_plot.js @@ -23,6 +23,7 @@ describe('LocusZoom.Plot', function() { d3.select('#plot').remove(); delete this.plot; }); + it('should allow for adding arbitrarily many panels', function() { const panelA = this.plot.addPanel({ id: 'panelA', foo: 'bar' }); assert.equal(panelA.id, 'panelA'); @@ -42,6 +43,7 @@ describe('LocusZoom.Plot', function() { assert.equal(this.plot.layout.panels[1].id, 'panelB'); assert.equal(this.plot.layout.panels[1].foo, 'baz'); }); + it('should allow for removing panels', function() { const panelA = this.plot.addPanel({ id: 'panelA', foo: 'bar', height: 10 }); const panelB = this.plot.addPanel({ id: 'panelB', foo: 'baz', height: 20 }); @@ -59,6 +61,7 @@ describe('LocusZoom.Plot', function() { assert.equal(this.plot._total_height, 20, 'Final height is the space requested by the remaining single panel'); }); + it('should allow setting dimensions', function() { this.plot.setDimensions(563, 681); assert.equal(this.plot.layout.width, 563); @@ -70,6 +73,7 @@ describe('LocusZoom.Plot', function() { this.plot.setDimensions('q', 0); assert.equal(this.plot.layout.width, 563, 'Non-numeric value is ignored'); }); + it('show rescale all panels equally when resizing the plot', function () { assert.equal(this.plot._total_height, 0, 'Empty plot has no height'); @@ -83,6 +87,7 @@ describe('LocusZoom.Plot', function() { assert.equal(panelA.layout.height, 200, 'Panel A doubles in size because plot doubles in size'); assert.equal(panelB.layout.height, 400, 'Panel B doubles in size because plot doubles in size'); }); + it('should rescale all panels and the plot, but only down to the specified minimum size', function () { assert.equal(this.plot._total_height, 0, 'Empty plot has no height'); @@ -96,6 +101,7 @@ describe('LocusZoom.Plot', function() { assert.equal(panelA.layout.height, 50, 'Panel A does not shrink below the minimum size'); assert.equal(panelB.layout.height, 100, 'Panel B does not shrink below the minimum size'); }); + it('should enforce consistent data layer widths and x-offsets across x-linked panels', function() { const layout = { width: 1000, @@ -105,14 +111,18 @@ describe('LocusZoom.Plot', function() { ], }; this.plot = populate('#plot', null, layout); - assert.equal(this.plot.layout.panels[0].margin.left, 200); - assert.equal(this.plot.layout.panels[1].margin.left, 200); - assert.equal(this.plot.layout.panels[0].margin.right, 300); - assert.equal(this.plot.layout.panels[1].margin.right, 300); - assert.equal(this.plot.layout.panels[0].cliparea.origin.x, 200); - assert.equal(this.plot.layout.panels[1].cliparea.origin.x, 200); - assert.equal(this.plot.layout.panels[0].origin.x, this.plot.layout.panels[0].origin.x); + + const panel0 = this.plot.layout.panels[0]; + const panel1 = this.plot.layout.panels[1]; + + assert.equal(panel0.margin.left, 200); + assert.equal(panel1.margin.left, 200, 'Adjusts second panel to match margins of first'); + assert.equal(panel0.margin.right, 300); + assert.equal(panel1.margin.right, 300); + assert.equal(panel0.cliparea.origin.x, 200); + assert.equal(panel1.cliparea.origin.x, 200); }); + it('should not allow for a non-numerical / non-positive predefined dimensions', function() { assert.throws(() => { populate('#plot', null, { width: 0 }); @@ -131,6 +141,7 @@ describe('LocusZoom.Plot', function() { layout.state = { chr: '1', start: 1, end: 100000 }; this.plot = populate('#plot', null, layout); }); + it('first child should be a mouse guide layer group element', function() { assert.equal(d3.select(this.plot.svg.node().firstChild).attr('id'), 'plot.mouse_guide'); }); @@ -148,6 +159,7 @@ describe('LocusZoom.Plot', function() { d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', datasources, layout); }); + it('Should allocate the space requested by the panel, even if less than plot height', function() { const panelA = { id: 'panelA', height: 50 }; this.plot.addPanel(panelA); @@ -158,6 +170,7 @@ describe('LocusZoom.Plot', function() { assert.equal(this.plot.panels.panelA.layout.height, 50); assert.equal(this.plot.panels.panelA.layout.origin.y, 0); }); + it('Should extend the size of the plot if panels are added that expand it, and automatically prevent panels from overlapping vertically', function() { const panelA = { id: 'panelA', height: 60 }; const panelB = { id: 'panelB', height: 60 }; @@ -172,6 +185,7 @@ describe('LocusZoom.Plot', function() { assert.equal(this.plot.panels.panelB.layout.height, 60); assert.equal(this.plot.panels.panelB.layout.origin.y, 60); }); + it('Should resize the plot as panels are removed', function() { const panelA = { id: 'panelA', height: 60 }; const panelB = { id: 'panelB', height: 60 }; @@ -185,6 +199,7 @@ describe('LocusZoom.Plot', function() { assert.equal(this.plot.panels.panelB.layout.height, 60); assert.equal(this.plot.panels.panelB.layout.origin.y, 0); }); + it('Should resize the plot as panels are removed, when panels specify min_height', function() { // Small hack; resize the plot after it was created this.plot.layout.height = 600; @@ -210,6 +225,7 @@ describe('LocusZoom.Plot', function() { // after panel C is removed. assert.equal(this.plot.panels.panelB.layout.origin.y, 300, 'Panel B origin.y matches layout value'); }); + it('Should resize the plot while retaining panel proportions when panel is removed, if plot min_height does not take precedence', function() { // When we remove a panel, we often want the plot to shrink by exactly that size. (so that the bottom // section simply disappears without changing the appearance of the panels that remain) But if plot @@ -236,6 +252,7 @@ describe('LocusZoom.Plot', function() { assert.equal(this.plot.panels.panelB.layout.height, 50, 'Panel B height matches layout (after)'); assert.equal(this.plot.panels.panelB.layout.origin.y, 300, 'Panel B origin.y appears immediately after panel A'); }); + it('Should allow for inserting panels at discrete y indexes', function() { const panelA = { id: 'panelA', height: 60 }; const panelB = { id: 'panelB', height: 61 }; @@ -254,6 +271,7 @@ describe('LocusZoom.Plot', function() { assert.deepEqual(panelA.height + panelB.height + panelC.height, this.plot._total_height, 'Plot height is equal to sum of panels'); }); + it('Should allow for inserting panels at negative discrete y indexes', function() { const panelA = { id: 'panelA', height: 60 }; const panelB = { id: 'panelB', height: 60 }; @@ -284,6 +302,7 @@ describe('LocusZoom.Plot', function() { d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', datasources, layout); }); + it('should show/hide/update on command and track shown status', function() { assert.isFalse(this.plot.curtain.showing); assert.isNull(this.plot.curtain.selector); @@ -300,12 +319,14 @@ describe('LocusZoom.Plot', function() { assert.isNull(this.plot.curtain.selector); assert.isNull(this.plot.curtain.content_selector); }); + it('should have a loader object with show/update/animate/setPercentCompleted/hide methods, a showing boolean, and selectors', function() { assert.isFalse(this.plot.loader.showing); assert.isNull(this.plot.loader.selector); assert.isNull(this.plot.loader.content_selector); assert.isNull(this.plot.loader.progress_selector); }); + it('should show/hide/update on command and track shown status', function() { assert.isFalse(this.plot.loader.showing); assert.isNull(this.plot.loader.selector); @@ -325,6 +346,7 @@ describe('LocusZoom.Plot', function() { assert.isNull(this.plot.loader.content_selector); assert.isNull(this.plot.loader.progress_selector); }); + it('should allow for animating or showing discrete percentages of completion', function() { this.plot.loader.show('test content').animate(); assert.isTrue(this.plot.loader.progress_selector.classed('lz-loader-progress-animated')); @@ -361,6 +383,7 @@ describe('LocusZoom.Plot', function() { this.layout = null; d3.select('#plot').remove(); }); + it('Should apply basic start/end state validation when necessary', function() { this.layout.state = { chr: 1, start: -60, end: 10300050 }; this.plot = populate('#plot', this.datasources, this.layout); @@ -369,6 +392,7 @@ describe('LocusZoom.Plot', function() { assert.equal(this.plot.state.end, 10300050); }); }); + it('Should apply minimum region scale state validation if set in the plot layout', function() { this.layout.min_region_scale = 2000; this.layout.state = { chr: 1, start: 10300000, end: 10300050 }; @@ -378,6 +402,7 @@ describe('LocusZoom.Plot', function() { assert.equal(this.plot.state.end, 10301025); }); }); + it('Should apply maximum region scale state validation if set in the plot layout', function() { this.layout.max_region_scale = 4000000; this.layout.state = { chr: 1, start: 10300000, end: 15300000 }; @@ -678,6 +703,7 @@ describe('LocusZoom.Plot', function() { assert.equal(stateB.start, 'foo'); assert.equal(stateB.end, 'bar'); }); + it('should enforce no zeros for start and end (if present with chr)', function() { let stateA = { chr: 1, start: 0, end: 123 }; stateA = _updateStatePosition(stateA); @@ -688,6 +714,7 @@ describe('LocusZoom.Plot', function() { assert.equal(stateB.start, 1); assert.equal(stateB.end, 1); }); + it('should enforce no negative values for start and end (if present with chr)', function() { let stateA = { chr: 1, start: -235, end: 123 }; stateA = _updateStatePosition(stateA); @@ -698,12 +725,14 @@ describe('LocusZoom.Plot', function() { assert.equal(stateB.start, 1); assert.equal(stateB.end, 1); }); + it('should enforce no non-integer values for start and end (if present with chr)', function() { let stateA = { chr: 1, start: 1234.4, end: 4567.8 }; stateA = _updateStatePosition(stateA); assert.equal(stateA.start, 1234); assert.equal(stateA.end, 4567); }); + it('should enforce no non-numeric values for start and end (if present with chr)', function() { let stateA = { chr: 1, start: 'foo', end: 324523 }; stateA = _updateStatePosition(stateA); diff --git a/test/unit/test_layouts.js b/test/unit/test_layouts.js index d94009e0..dd4dd806 100644 --- a/test/unit/test_layouts.js +++ b/test/unit/test_layouts.js @@ -2,6 +2,8 @@ import { assert } from 'chai'; import LAYOUTS, {_LayoutRegistry} from '../../esm/registry/layouts'; import {deepCopy, merge} from '../../esm/helpers/layouts'; +import clone from 'just-clone'; + describe('_LayoutRegistry', function() { describe('Provides a method to list current layouts by type', function() { it ('No argument: returns an object, keys are layout types and values are arrays of layout names', function() { @@ -126,30 +128,31 @@ describe('_LayoutRegistry', function() { assert.deepEqual(lookup.get('test', 'test', mods), expected_layout); }); - it('Allows for namespacing arbitrary keys and values at all nesting levels', function() { + it.skip('Allows for namespacing arbitrary keys and values at all nesting levels', function() { + //FIXME: This test references a lot of beheavior that was deprecated/ removed in modern LZ const lookup = new _LayoutRegistry(); const base_layout = { scalar_0: 123, '{{namespace}}scalar_1': 'aardvark', - '{{namespace[dingo]}}scalar_2': '{{namespace[1]}}albacore', + 'dingo:scalar_2': '1:albacore', namespace_scalar: '{{namespace}}badger', - namespace_0_scalar: '{{namespace[0]}}crocodile', - namespace_dingo_scalar: '{{namespace[dingo]}}emu', - namespace_1_scalar: '{{namespace[1]}}ferret', + namespace_0_scalar: '0:crocodile', + namespace_dingo_scalar: 'dingo:emu', + namespace_1_scalar: '1:ferret', array_of_scalars: [ 4, 5, 6 ], nested_object: { property_0: { scalar_0: 0, scalar_1: 'grackle', - namespace_scalar: '{{{{namespace}}hog}} and {{{{namespace[1]}}yak}} and {{{{namespace[jackal]}}zebu}}', - namespace_0_scalar: '{{namespace[0]}}iguana', - namespace_jackal_scalar: '{{namespace[jackal]}}kangaroo', - namespace_dingo_scalar: '{{namespace[dingo]}}lemur', - namespace_1_scalar: '{{namespace[1]}}moose', - array: ['nematoad', '{{namespace}}oryx', '{{namespace[1]}}pigeon', '{{namespace[jackal]}}quail'], + namespace_scalar: '{{{{namespace}}hog}} and {{1:yak}} and {{jackal:zebu}}', + namespace_0_scalar: '0:iguana', + namespace_jackal_scalar: 'jackal:kangaroo', + namespace_dingo_scalar: 'dingo:lemur', + namespace_1_scalar: '1:moose', + array: ['nematoad', 'oryx', '1:pigeon', 'jackal:quail'], object: { scalar: 'rhea', - array: ['serpent', '{{namespace[0]}}tortoise', '{{namespace[upapa]}}vulture', '{{namespace}}xerus'], + array: ['serpent', '0:tortoise', 'upapa:vulture', '{{namespace}}xerus'], }, }, property_1: false, @@ -203,7 +206,7 @@ describe('_LayoutRegistry', function() { assert.equal(single_namespace_layout.nested_object.property_0.object.array[3], 'ns:xerus'); // Array of namespaces: replace number-indexed namespace holders, - // resolve {{namespace}} and any named namespaces to {{namespace[0]}}. + // resolve {{namespace}} and any named namespaces to 0:. const array_namespace_layout = lookup.get('test', 'test', { namespace: ['ns_0', 'ns_1'] }); assert.equal(array_namespace_layout['ns_0:scalar_1'], 'aardvark'); assert.equal(array_namespace_layout['ns_0:scalar_2'], 'ns_1:albacore'); @@ -250,35 +253,8 @@ describe('_LayoutRegistry', function() { assert.equal(object_namespace_layout.nested_object.property_0.object.array[3], 'ns_default:xerus'); }); - it('Allows for inheriting namespaces', function() { - const lookup = new _LayoutRegistry(); - - const layout_0 = { - namespace: { dingo: 'ns_dingo', jackal: 'ns_jackal', default: 'ns_0' }, - '{{namespace[dingo]}}scalar_1': 'aardvark', - '{{namespace[dingo]}}scalar_2': '{{namespace[jackal]}}albacore', - scalar_3: '{{namespace}}badger', - }; - lookup.add('test', 'layout_0', layout_0); - - const layout_1 = { - namespace: { ferret: 'ns_ferret', default: 'ns_1' }, - '{{namespace}}scalar_1': 'emu', - '{{namespace[ferret]}}scalar_2': '{{namespace}}kangaroo', - nested_layout: lookup.get('test', 'layout_0', { unnamespaced: true }), - }; - lookup.add('test', 'layout_1', layout_1); - const ns_layout_1 = lookup.get('test', 'layout_1', { - namespace: { - dingo: 'ns_dingo_mod', - default: 'ns_mod', - }, - }); - assert.equal(ns_layout_1['ns_mod:scalar_1'], 'emu'); - assert.equal(ns_layout_1['ns_ferret:scalar_2'], 'ns_mod:kangaroo'); - assert.equal(ns_layout_1.nested_layout['ns_dingo_mod:scalar_1'], 'aardvark'); - assert.equal(ns_layout_1.nested_layout['ns_dingo_mod:scalar_2'], 'ns_jackal:albacore'); - assert.equal(ns_layout_1.nested_layout.scalar_3, 'ns_mod:badger'); + it.skip('Allows for inheriting namespaces', function() { + assert.ok(false, 'TODO: Reimplement with new namespace behavior'); }); }); }); @@ -306,18 +282,6 @@ describe('Layout helpers', function () { }; }); - it('should throw an exception if either argument is not an object', function() { - assert.throws(() => { - merge(); - }); - assert.throws(() => { - merge({}); - }); - assert.throws(() => { - merge({}, ''); - }); - }); - it('should return the passed default layout if provided an empty layout', function() { const returned_layout = merge({}, this.default_layout); assert.deepEqual(returned_layout, this.default_layout); From 03ffafae97d6d00d647166ac90de5496e2d719bf Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Fri, 10 Sep 2021 17:38:21 -0400 Subject: [PATCH 018/100] Strip more namespace usages from tests --- test/unit/ext/test_ext_intervals-track.js | 4 ++-- test/unit/helpers/test_layouts.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/unit/ext/test_ext_intervals-track.js b/test/unit/ext/test_ext_intervals-track.js index d6aa9bfd..42894129 100644 --- a/test/unit/ext/test_ext_intervals-track.js +++ b/test/unit/ext/test_ext_intervals-track.js @@ -27,12 +27,12 @@ describe('Interval annotation track', function () { const layout = LAYOUTS.get('data_layer', 'intervals', { // Unit tests will use the most rigorous form of the track (coloring and separation are determined by // a unique ID that is separate from the label) - track_split_field: '{{namespace[intervals]}}state_id', + track_split_field: 'intervals:state_id', }); const instance = DATA_LAYERS.create('intervals', layout); this.instance = instance; this.color_config = find_color_options(instance._base_layout); - this.color_config.field = '{{namespace[intervals]}}state_id'; + this.color_config.field = 'intervals:state_id'; this.legend_config = instance._base_layout.legend; }); diff --git a/test/unit/helpers/test_layouts.js b/test/unit/helpers/test_layouts.js index 8ae1c93e..6786e4b9 100644 --- a/test/unit/helpers/test_layouts.js +++ b/test/unit/helpers/test_layouts.js @@ -138,13 +138,13 @@ describe('Layout helper functions', function () { it('works with abstract layouts and namespace syntax', function () { let base = { - field: '{{namespace[family]}}old_name', - template: '{{{{namespace[family]}}old_name}} was here', + field: 'family:old_name', + template: '{{family:old_name}} was here', }; - base = renameField(base, '{{namespace[family]}}old_name', '{{namespace[family]}}moon_unit'); + base = renameField(base, 'family:old_name', 'family:moon_unit'); assert.deepEqual(base, { - field: '{{namespace[family]}}moon_unit', - template: '{{{{namespace[family]}}moon_unit}} was here', + field: 'family:moon_unit', + template: '{{family:moon_unit}} was here', }); }); From fc61e12f32544d1c4f5da1bf1729aafc486b6559 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 14 Sep 2021 13:35:47 -0400 Subject: [PATCH 019/100] New namespaces and requester functionality [ci skip] --- esm/components/data_layer/base.js | 21 +++--- esm/data/requester.js | 115 +++++++++++++++++++++--------- esm/helpers/layouts.js | 64 ++++++----------- esm/layouts/index.js | 54 +++++++------- esm/registry/layouts.js | 29 ++++---- test/unit/data/test_requester.js | 57 ++++++++------- 6 files changed, 184 insertions(+), 156 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 002f3d88..fd32f444 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -84,7 +84,8 @@ const default_layout = { id: '', type: '', tag: 'custom_data_type', - fields: [], + namespace: {}, + data_operations: [], id_field: 'id', filters: null, match: {}, @@ -292,10 +293,11 @@ class BaseDataLayer { 'hidden': false, }; - // On first load, track all data behaviors and options + // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval this._data_contract = new Set(); // List of all fields requested by the layout - - this.mutateLayout(); + this._entities = new Map(); + this._dependencies = []; + this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout } /****** Public interface: methods for manipulating the layer from other parts of LZ */ @@ -379,10 +381,11 @@ class BaseDataLayer { */ mutateLayout() { // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract. - const { namespace } = this.layout; - if (namespace) { - this._data_contract = findFields(this.layout, Object.keys(namespace)); - } + const { namespace, data_operations } = this.layout; + this._data_contract = findFields(this.layout, Object.keys(namespace)); + const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations); + this._entities = entities; + this._dependencies = dependencies; } /********** Protected methods: useful in subclasses to manipulate data layer behaviors */ @@ -1515,7 +1518,7 @@ class BaseDataLayer { // and then recreated if returning to visibility // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads) - return this.parent_plot.lzd.getData(this.state, this.layout.namespace || {}, this.layout.join_options || []) + return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies) .then((new_data) => { this.data = new_data; this.applyDataMethods(); diff --git a/esm/data/requester.js b/esm/data/requester.js index ea37c4a6..8cb4d91e 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -10,6 +10,8 @@ class JoinTask { } getData(options, left, right) { + // Future: this could support joining > 2 things in one function. + // Joining three things at once is a pretty niche usage, and we won't add this complexity unless clearly needed return Promise.resolve(this._callable(left, right, ...this._params)); } } @@ -32,50 +34,99 @@ class Requester { this._sources = sources; } - _config_to_sources(namespace_options, join_options) { - // 1. Find the data sources needed for this request, and add in the joins for this layer - // namespaces: { assoc: assoc, ld(assoc): ld } - // TODO: Move this to the data layer creation step, along with contract validation - // Dependencies are defined as raw data + joins - const dependencies = []; - // Create a list of sources unique to this layer, including both fetch and join operations + /** + * Parse the data layer configuration when a layer is first created. + * Validate config, and return entities and dependencies in a format usable for data retrieval. + * This is used by data layers, and also other data-retrieval functions (like subscribeToDate). + * + * Inherent assumptions: + * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed. + * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be + * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the + * removed adapter. + * @param {Object} namespace_options + * @param {Array} data_operations + * @returns {(Map|*[])[]} + */ + config_to_sources(namespace_options = {}, data_operations = []) { const entities = new Map(); - Object.entries(namespace_options) - .forEach(([label, source_name]) => { - // In layout syntax, namespace names and dependencies are written together, like ld = ld(assoc). Convert. - let match = label.match(/^(\w+)$|^(\w+)\(/); - if (!match) { - throw new Error(`Invalid namespace name: '${label}'. Should be 'somename' or 'somename(somedep)'`); - } - const entity_label = match[1] || match[2]; + const namespace_local_names = Object.keys(namespace_options); - const source = this._sources.get(source_name); + // 1. Specify how to coordinate data. Precedence: + // a) EXPLICIT fetch logic, + // b) IMPLICIT auto-generate fetch order if there is only one NS, + // c) Throw "spec required" error if > 1, because 2 adapters may need to be fetched in a sequence + let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from} + if (!dependency_order) { + dependency_order = { type: 'fetch', from: namespace_local_names }; + data_operations.unshift(dependency_order); + } - if (entities.has(entity_label)) { - throw new Error(`Configuration error: within a layer, namespace name '${label}' must be unique`); - } + // Validate that all NS items are available to the root requester in DataSources. All layers recognize a + // default value, eg people copying the examples tend to have defined a datasource called "assoc" + const ns_pattern = /^\w+$/; + for (let [local_name, global_name] of Object.entries(namespace_options)) { + if (!ns_pattern.test(local_name)) { + throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`); + } - entities.set(entity_label, source); - dependencies.push(label); - }); + const source = this._sources.get(global_name); + if (!source) { + throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`); + } + entities.set(local_name, source); - join_options.forEach((config) => { - const {type, name, requires, params} = config; - if (entities.has(name)) { - throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`); + // Note: Dependency spec checker will consider "ld(assoc)" to match a namespace called "ld" + if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) { + // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join. + // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch + // Thus the default behavior is "fetch all namespaces as though they don't depend on anything. + // If they depend on something, only then does "data_ops[@type=fetch].from" need to be mutated + dependency_order.from.push(local_name); } - const task = new JoinTask(type, params); - entities.set(name, task); - dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB) - }); + } + + let dependencies = Array.from(dependency_order.from); + + // Now check all joins. Are namespaces valid? Are they requesting known data? + const namecount = 0; + for (let config of data_operations) { + let {type, name, requires, params} = config; + if (!name) { + name = config.name = `join${namecount}`; + } + if (type !== 'fetch') { + if (entities.has(name)) { + throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`); + } + requires.forEach((require_name) => { + if (!entities.has(require_name)) { + throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`); + } + }); + + const task = new JoinTask(type, params); + entities.set(name, task); + dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB) + } + } return [entities, dependencies]; } - getData(state, namespace_options, join_options) { - const [entities, dependencies] = this._config_to_sources(namespace_options, join_options); + /** + * + * @param {Object} state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end) + * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts. + * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances + * (things that implement a method getData). + * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order + * @returns {Promise<*[]>|*} + */ + getData(state, entities, dependencies) { if (!dependencies.length) { return Promise.resolve([]); } + // The last dependency (usually the last join operation) determines the last thing returned. return getLinkedData(state, entities, dependencies, true); } } diff --git a/esm/helpers/layouts.js b/esm/helpers/layouts.js index 632cc099..e300c322 100644 --- a/esm/helpers/layouts.js +++ b/esm/helpers/layouts.js @@ -21,52 +21,28 @@ const triangledown = { }; /** - * Apply namespaces to layout, recursively + * Apply shared namespaces to a layout, recursively * @private */ -function applyNamespaces(element, namespace, default_namespace) { - if (namespace) { - if (typeof namespace == 'string') { - namespace = { default: namespace }; - } - } else { - namespace = { default: '' }; +function applyNamespaces(layout, shared_namespaces) { + shared_namespaces = shared_namespaces || {}; + if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') { + throw new Error('Layout and shared namespaces must be provided as objects'); } - if (typeof element == 'string') { - const re = /\{\{namespace(\[[A-Za-z_0-9]+\]|)\}\}/g; - let match, base, key, resolved_namespace; - const replace = []; - while ((match = re.exec(element)) !== null) { - base = match[0]; - key = match[1].length ? match[1].replace(/(\[|\])/g, '') : null; - resolved_namespace = default_namespace; - if (namespace != null && typeof namespace == 'object' && typeof namespace[key] != 'undefined') { - resolved_namespace = namespace[key] + (namespace[key].length ? ':' : ''); - } - replace.push({ base: base, namespace: resolved_namespace }); - } - for (let r in replace) { - element = element.replace(replace[r].base, replace[r].namespace); - } - } else if (typeof element == 'object' && element != null) { - if (typeof element.namespace != 'undefined') { - const merge_namespace = (typeof element.namespace == 'string') ? { default: element.namespace } : element.namespace; - namespace = merge(namespace, merge_namespace); - } - let namespaced_element, namespaced_property; - for (let property in element) { - if (property === 'namespace') { - continue; - } - namespaced_element = applyNamespaces(element[property], namespace, default_namespace); - namespaced_property = applyNamespaces(property, namespace, default_namespace); - if (property !== namespaced_property) { - delete element[property]; - } - element[namespaced_property] = namespaced_element; + + for (let [field_name, item] of Object.entries(layout)) { + if (field_name === 'namespace') { + Object.keys(item).forEach((requested_ns) => { + const override = shared_namespaces[requested_ns]; + if (override) { + item[requested_ns] = override; + } + }); + } else if (item !== null && (typeof item === 'object')) { + layout[field_name] = applyNamespaces(item, shared_namespaces); } } - return element; + return layout; } /** @@ -167,11 +143,11 @@ function findFields(layout, prefixes, field_finder = null) { const fields = new Set(); for (const value of Object.values(layout)) { - const key_type = typeof value; + const value_type = typeof value; let matches; - if (key_type === 'string') { + if (value_type === 'string') { matches = [...value.matchAll(field_finder)].map((m) => m[1]); - } else if (value !== null && key_type === 'object') { + } else if (value !== null && value_type === 'object') { matches = findFields(value, prefixes, field_finder); } else { // Only look for field names in strings or compound values diff --git a/esm/layouts/index.js b/esm/layouts/index.js index 5df77185..e7404615 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -23,7 +23,6 @@ const LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6) * Tooltip Layouts */ const standard_association_tooltip = { - namespace: { 'assoc': 'assoc' }, closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, @@ -66,7 +65,6 @@ const standard_genes_tooltip = { }; const catalog_variant_tooltip = { - namespace: { 'assoc': 'assoc', 'catalog': 'catalog' }, closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, @@ -79,7 +77,6 @@ const catalog_variant_tooltip = { }; const coaccessibility_tooltip = { - namespace: { 'access': 'access' }, closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, @@ -119,7 +116,6 @@ const recomb_rate_layer = { id: 'recombrate', type: 'line', tag: 'recombination', - fields: ['recomb:position', 'recomb:recomb_rate'], z_index: 1, style: { 'stroke': '#0000FF', @@ -142,18 +138,22 @@ const recomb_rate_layer = { * @type data_layer */ const association_pvalues_layer = { - // FIXME: current design requires people to specify dependencies whenever overriding a namespace. That's a little unintuitive; alternatives? - namespace: { 'assoc': 'assoc', 'ld(assoc)': 'ld' }, - join_options: [{ - type: 'left_match', - name: 'combined', - requires: ['assoc', 'ld'], - params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later. - }], + namespace: { 'assoc': 'assoc', 'ld': 'ld' }, + data_operations: [ + { + type: 'fetch', + from: ['assoc', 'ld(assoc)'], + }, + { + type: 'left_match', + name: 'combined', + requires: ['assoc', 'ld'], + params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later. + }, + ], id: 'associationpvalues', type: 'scatter', tag: 'association', - fields: ['assoc:variant', 'assoc:position', 'assoc:log_pvalue', 'assoc:log_pvalue|logtoscinotation', 'assoc:ref_allele'], id_field: 'assoc:variant', coalesce: { active: true, @@ -241,7 +241,6 @@ const coaccessibility_layer = { id: 'coaccessibility', type: 'arcs', tag: 'coaccessibility', - fields: ['access:start1', 'access:end1', 'access:start2', 'access:end2', 'access:id', 'access:target', 'access:score'], match: { send: 'access:target', receive: 'access:target' }, id_field: 'access:id', filters: [ @@ -305,7 +304,7 @@ const association_pvalues_catalog_layer = function () { let base = deepCopy(association_pvalues_layer); base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base); - base.join_options.push({ + base.data_operations.push({ type: 'assoc_to_gwas_catalog', name: 'assoc_catalog', requires: ['combined', 'catalog'], @@ -314,7 +313,6 @@ const association_pvalues_catalog_layer = function () { base.tooltip.html += '{{#if catalog:rsid}}
See hits in GWAS catalog{{/if}}'; base.namespace.catalog = 'catalog'; - base.fields.push('catalog:rsid', 'catalog:trait', 'catalog:log_pvalue'); return base; }(); @@ -332,7 +330,6 @@ const phewas_pvalues_layer = { point_size: 70, tooltip_positioning: 'vertical', id_field: 'phewas:id', - fields: ['phewas:id', 'phewas:log_pvalue', 'phewas:trait_group', 'phewas:trait_label'], x_axis: { field: 'phewas:x', // Synthetic/derived field added by `category_scatter` layer category_field: 'phewas:trait_group', @@ -406,12 +403,17 @@ const phewas_pvalues_layer = { * @type data_layer */ const genes_layer = { - namespace: { 'gene': 'gene', 'constraint(gene)': 'constraint' }, - join_options: [{ - type: 'genes_to_gnomad_constraint', - name: 'combined', - requires: ['gene', 'constraint'], - }], + namespace: { 'gene': 'gene', 'constraint': 'constraint' }, + data_operations: [ + { + type: 'fetch', + from: ['gene', 'constraint(gene)'], + }, + { + type: 'genes_to_gnomad_constraint', + requires: ['gene', 'constraint'], + }, + ], id: 'genes', type: 'genes', tag: 'genes', @@ -479,11 +481,6 @@ const annotation_catalog_layer = { field: 'assoc:position', }, color: '#0000CC', - fields: [ - 'assoc:variant', 'assoc:chromosome', 'assoc:position', - 'catalog:variant', 'catalog:rsid', 'catalog:trait', - 'catalog:log_pvalue', 'catalog:pos', - ], filters: [ // Specify which points to show on the track. Any selection must satisfy ALL filters { field: 'catalog:rsid', operator: '!=', value: null }, @@ -793,7 +790,6 @@ const association_catalog_panel = function () { let base = deepCopy(association_panel); base = merge({ id: 'associationcatalog', - namespace: { 'assoc': 'assoc', 'ld(assoc)': 'ld', 'catalog': 'catalog' }, // Required to resolve display options }, base); base.toolbar.widgets.push({ diff --git a/esm/registry/layouts.js b/esm/registry/layouts.js index fc4a42bb..aee511fb 100644 --- a/esm/registry/layouts.js +++ b/esm/registry/layouts.js @@ -19,26 +19,21 @@ class LayoutRegistry extends RegistryBase { throw new Error('Must specify both the type and name for the layout desired. See .list() for available options'); } // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as - // applying overrides or using namespaces to convert an abstract layout into a concrete one. + // applying overrides or applying namespaces. let base = super.get(type).get(name); - base = merge(overrides, base); - if (base.unnamespaced) { - delete base.unnamespaced; - return deepCopy(base); - } - let default_namespace = ''; - if (typeof base.namespace == 'string') { - default_namespace = base.namespace; - } else if (typeof base.namespace == 'object' && Object.keys(base.namespace).length) { - if (typeof base.namespace.default != 'undefined') { - default_namespace = base.namespace.default; - } else { - default_namespace = base.namespace[Object.keys(base.namespace)[0]].toString(); - } + + // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides. + // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced) + const custom_namespaces = overrides.namespace; + if (!base.namespace) { + // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout + delete overrides.namespace; } - default_namespace += default_namespace.length ? ':' : ''; - const result = applyNamespaces(base, base.namespace, default_namespace); + let result = merge(overrides, base); + if (custom_namespaces) { + result = applyNamespaces(base, custom_namespaces); + } return deepCopy(result); } diff --git a/test/unit/data/test_requester.js b/test/unit/data/test_requester.js index 73be73cc..17b5ac64 100644 --- a/test/unit/data/test_requester.js +++ b/test/unit/data/test_requester.js @@ -23,17 +23,20 @@ describe('Requester object defines and parses requests', function () { this._requester = new Requester(this._all_datasources); }); - it('converts layout configuration entities and dependencies', function () { + it('builds request data from namespaces and join tasks', function () { // Test name logic - const namespace_options = {'assoc': 'assoc1', 'ld(assoc)': 'someld'}; - const join_options = [{ - type: 'left_match', - name: 'combined', - requires: ['assoc', 'ld'], - params: ['assoc:variant', 'ld:variant'], - }]; + const namespace_options = {'assoc': 'assoc1', 'ld': 'someld'}; + const data_operations = [ + { type: 'fetch', from: ['assoc', 'ld(assoc)'] }, + { + type: 'left_match', + name: 'combined', + requires: ['assoc', 'ld'], + params: ['assoc:variant', 'ld:variant'], + }, + ]; - const [entities, dependencies] = this._requester._config_to_sources(namespace_options, join_options); + const [entities, dependencies] = this._requester.config_to_sources(namespace_options, data_operations); // Validate names of dependencies are correct assert.deepEqual(dependencies, ['assoc', 'ld(assoc)', 'combined(assoc, ld)'], 'Dependencies are resolved in expected order'); @@ -49,32 +52,36 @@ describe('Requester object defines and parses requests', function () { it('provides developer friendly error messages', function () { // Test parse errors: namespaces malformed assert.throws(() => { - this._requester._config_to_sources({ 'not:allowed': 'whatever' }, {}); + this._requester.config_to_sources({ 'not:allowed': 'whatever' }, []); }, /Invalid namespace name: 'not:allowed'/); - // Test duplicate namespace errors: assoc - assert.throws(() => { - this._requester._config_to_sources({ 'assoc': {}, 'assoc(dep)': 'this is weird and not supported' }, []); - }, /namespace name 'assoc' must be unique/); - // Test duplicate namespace errors: joins assert.throws(() => { - this._requester._config_to_sources( + this._requester.config_to_sources( {}, - [{name: 'combined', type: 'left_match', requires: []}, {name: 'combined', type: 'left_match', requires: []}] + [ + {name: 'combined', type: 'left_match', requires: []}, + {name: 'combined', type: 'left_match', requires: []}, + ] ); }, /join name 'combined' must be unique/); }); it('performs joins based on layout spec', function () { - const namespace_options = {'assoc': 'assoc1', 'ld(assoc)': 'someld'}; - const join_options = [{ - type: 'sumtwo', - name: 'combined', - requires: ['assoc', 'ld'], - params: [3], // tests that params get passed, and can be whatever a join function needs - }]; - return this._requester.getData({}, namespace_options, join_options) + const namespace_options = {'assoc': 'assoc1', 'ld': 'someld'}; + const data_operations = [ + { type: 'fetch', from: ['assoc', 'ld(assoc)'] }, + { + type: 'sumtwo', + name: 'combined', + requires: ['assoc', 'ld'], + params: [3], // tests that params get passed, and can be whatever a join function needs + }, + ]; + + const [entities, dependencies] = this._requester.config_to_sources(namespace_options, data_operations); + + return this._requester.getData({}, entities, dependencies) .then((res) => { assert.equal(res, 6); }); From 0e093d95d1ac668f1d70afd0c72943a648cb30c2 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 15 Sep 2021 12:16:04 -0400 Subject: [PATCH 020/100] Add requester-generation logic and tests. Revise test suite to decouple dependencies and reflect removal of manually curated `fields` array --- esm/components/data_layer/base.js | 12 +- esm/data/requester.js | 10 +- esm/registry/layouts.js | 4 +- .../data_layer/test_highlight_regions.js | 2 +- test/unit/components/test_datalayer.js | 61 +++-- test/unit/components/test_panel.js | 23 +- test/unit/components/test_plot.js | 26 ++- test/unit/data/test_requester.js | 89 ++++++- test/unit/test_layouts.js | 220 ++++++++---------- 9 files changed, 263 insertions(+), 184 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index fd32f444..c3beb9e5 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -381,11 +381,13 @@ class BaseDataLayer { */ mutateLayout() { // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract. - const { namespace, data_operations } = this.layout; - this._data_contract = findFields(this.layout, Object.keys(namespace)); - const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations); - this._entities = entities; - this._dependencies = dependencies; + if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester + const { namespace, data_operations } = this.layout; + this._data_contract = findFields(this.layout, Object.keys(namespace)); + const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations); + this._entities = entities; + this._dependencies = dependencies; + } } /********** Protected methods: useful in subclasses to manipulate data layer behaviors */ diff --git a/esm/data/requester.js b/esm/data/requester.js index 8cb4d91e..d449db5b 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -89,13 +89,15 @@ class Requester { let dependencies = Array.from(dependency_order.from); // Now check all joins. Are namespaces valid? Are they requesting known data? - const namecount = 0; for (let config of data_operations) { let {type, name, requires, params} = config; - if (!name) { - name = config.name = `join${namecount}`; - } if (type !== 'fetch') { + let namecount = 0; + if (!name) { + name = config.name = `join${namecount}`; + namecount += 1; + } + if (entities.has(name)) { throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`); } diff --git a/esm/registry/layouts.js b/esm/registry/layouts.js index aee511fb..464b5fd6 100644 --- a/esm/registry/layouts.js +++ b/esm/registry/layouts.js @@ -27,12 +27,14 @@ class LayoutRegistry extends RegistryBase { const custom_namespaces = overrides.namespace; if (!base.namespace) { // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout + // NOTE: The "merge namespace" behavior means that data layers can add new data easily, but this method + // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately). delete overrides.namespace; } let result = merge(overrides, base); if (custom_namespaces) { - result = applyNamespaces(base, custom_namespaces); + result = applyNamespaces(result, custom_namespaces); } return deepCopy(result); } diff --git a/test/unit/components/data_layer/test_highlight_regions.js b/test/unit/components/data_layer/test_highlight_regions.js index 41323edc..90d5a27f 100644 --- a/test/unit/components/data_layer/test_highlight_regions.js +++ b/test/unit/components/data_layer/test_highlight_regions.js @@ -97,8 +97,8 @@ describe('highlight_regions data layer', function () { start_field: 'intervals:start', end_field: 'intervals:end', regions: [], - fields: ['intervals:start', 'intervals:end'], }); + layer.mutateLayout(); // Manually tell the layer that data rules have changed for this specific test return this.plot.applyState().then(() => { assert.equal(layer.svg.group.selectAll('rect').size(), 1, 'Layer draws one region as pulled from the datasource'); }); diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index 1d1f2b9b..fd4670ab 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -477,7 +477,6 @@ describe('LocusZoom.DataLayer', function () { { namespace: { d: 'd' }, id: 'd', - fields: ['d:id'], id_field: 'd:id', type: 'scatter', highlighted: { onmouseover: 'toggle' }, @@ -496,7 +495,7 @@ describe('LocusZoom.DataLayer', function () { }); it('should allow for highlighting and unhighlighting a single element', function () { - return this.plot.lzd.getData({}, { d: 'd' }, []) + return this.plot.applyState() .then(() => { const state_id = this.plot.panels.p.data_layers.d.state_id; const layer_state = this.plot.state[state_id]; @@ -530,23 +529,23 @@ describe('LocusZoom.DataLayer', function () { }); it('should allow for highlighting and unhighlighting all elements', function () { - return this.plot.lzd.getData({}, { d: 'd' }, []) + return this.plot.applyState() .then(() => { - const state_id = this.plot.panels.p.data_layers.d.state_id; + const layer = this.plot.panels.p.data_layers.d; + const state_id = layer.state_id; const layer_state = this.plot.state[state_id]; - const d = this.plot.panels.p.data_layers.d; - const a_id = d.getElementId(d.data[0]); - const b_id = d.getElementId(d.data[1]); - const c_id = d.getElementId(d.data[2]); + const a_id = layer.getElementId(layer.data[0]); + const b_id = layer.getElementId(layer.data[1]); + const c_id = layer.getElementId(layer.data[2]); - this.plot.panels.p.data_layers.d.highlightAllElements(); + layer.highlightAllElements(); const highlight_flags = layer_state.status_flags.highlighted; assert.equal(highlight_flags.size, 3); assert.ok(highlight_flags.has(a_id)); assert.ok(highlight_flags.has(b_id)); assert.ok(highlight_flags.has(c_id)); - this.plot.panels.p.data_layers.d.unhighlightAllElements(); + layer.unhighlightAllElements(); assert.equal(layer_state.status_flags.highlighted.size, 0); }); }); @@ -584,57 +583,57 @@ describe('LocusZoom.DataLayer', function () { }); it('should allow for selecting and unselecting a single element', function () { - return this.plot.lzd.getData({}, { d: 'd' }, []) + return this.plot.applyState() .then(() => { - const state_id = this.plot.panels.p.data_layers.d.state_id; + const layer = this.plot.panels.p.data_layers.d; + const state_id = layer.state_id; const layer_state = this.plot.state[state_id]; - const d = this.plot.panels.p.data_layers.d; - const a = d.data[0]; - const a_id = d.getElementId(a); - const b = d.data[1]; - const c = d.data[2]; - const c_id = d.getElementId(c); + const a = layer.data[0]; + const a_id = layer.getElementId(a); + const b = layer.data[1]; + const c = layer.data[2]; + const c_id = layer.getElementId(c); const selected_flags = layer_state.status_flags.selected; assert.equal(selected_flags.size, 0); - this.plot.panels.p.data_layers.d.selectElement(a); + layer.selectElement(a); assert.equal(selected_flags.size, 1); assert.ok(selected_flags.has(a_id)); - this.plot.panels.p.data_layers.d.unselectElement(a); + layer.unselectElement(a); assert.equal(selected_flags.size, 0); - this.plot.panels.p.data_layers.d.selectElement(c); + layer.selectElement(c); assert.equal(selected_flags.size, 1); assert.ok(selected_flags.has(c_id)); - this.plot.panels.p.data_layers.d.unselectElement(b); + layer.unselectElement(b); assert.equal(selected_flags.size, 1); - this.plot.panels.p.data_layers.d.unselectElement(c); + layer.unselectElement(c); assert.equal(selected_flags.size, 0); }); }); it('should allow for selecting and unselecting all elements', function () { - return this.plot.lzd.getData({}, { d: 'd' }, []) + return this.plot.applyState() .then(() => { - const state_id = this.plot.panels.p.data_layers.d.state_id; + const layer = this.plot.panels.p.data_layers.d; + const state_id = layer.state_id; const layer_state = this.plot.state[state_id]; - const d = this.plot.panels.p.data_layers.d; - const a_id = d.getElementId(d.data[0]); - const b_id = d.getElementId(d.data[1]); - const c_id = d.getElementId(d.data[2]); + const a_id = layer.getElementId(layer.data[0]); + const b_id = layer.getElementId(layer.data[1]); + const c_id = layer.getElementId(layer.data[2]); - this.plot.panels.p.data_layers.d.selectAllElements(); + layer.selectAllElements(); const selected_flags = layer_state.status_flags.selected; assert.equal(selected_flags.size, 3); assert.ok(selected_flags.has(a_id)); assert.ok(selected_flags.has(b_id)); assert.ok(selected_flags.has(c_id)); - this.plot.panels.p.data_layers.d.unselectAllElements(); + layer.unselectAllElements(); assert.equal(layer_state.status_flags.selected.size, 0); }); }); diff --git a/test/unit/components/test_panel.js b/test/unit/components/test_panel.js index 8e7473e5..11702662 100644 --- a/test/unit/components/test_panel.js +++ b/test/unit/components/test_panel.js @@ -13,9 +13,13 @@ describe('Panel', function() { describe('Constructor', function() { beforeEach(function() { d3.select('body').append('div').attr('id', 'plot_id'); - const layout = LAYOUTS.get('plot', 'standard_association'); - layout.state = { chr: '1', start: 1, end: 100000 }; - this.plot = populate('#plot_id', null, layout); + const layout = { + state: {}, + width: 800, + panels: [], + }; + + this.plot = populate('#plot_id', new DataSources(), layout); this.panel = this.plot.panels.association; }); @@ -49,9 +53,18 @@ describe('Panel', function() { describe('Geometry Methods', function() { beforeEach(function() { d3.select('body').append('div').attr('id', 'plot_id'); - const layout = LAYOUTS.get('plot', 'standard_association'); + + const layout = { + state: {}, + width: 800, + panels: [ + { id: 'association', height: 225, data_layers: [] }, + { id: 'genes', height: 225, data_layers: [] }, + ], + }; + layout.state = { chr: '1', start: 1, end: 100000 }; - this.plot = populate('#plot_id', null, layout); + this.plot = populate('#plot_id', new DataSources(), layout); this.association_panel = this.plot.panels.association; this.genes_panel = this.plot.panels.genes; }); diff --git a/test/unit/components/test_plot.js b/test/unit/components/test_plot.js index 49fbc076..30abd54c 100644 --- a/test/unit/components/test_plot.js +++ b/test/unit/components/test_plot.js @@ -5,7 +5,6 @@ import sinon from 'sinon'; import Plot, {_updateStatePosition} from '../../../esm/components/plot'; import DataSources from '../../../esm/data'; import {populate} from '../../../esm/helpers/display'; -import { LAYOUTS } from '../../../esm/registry'; describe('LocusZoom.Plot', function() { // Tests @@ -17,7 +16,7 @@ describe('LocusZoom.Plot', function() { panels: [], }; d3.select('body').append('div').attr('id', 'plot'); - this.plot = populate('#plot', null, layout); + this.plot = populate('#plot', new DataSources(), layout); }); afterEach(function() { d3.select('#plot').remove(); @@ -106,11 +105,19 @@ describe('LocusZoom.Plot', function() { const layout = { width: 1000, panels: [ - LAYOUTS.get('panel', 'association', { margin: { left: 200 } }), - LAYOUTS.get('panel', 'association', { id: 'assoc2', margin: { right: 300 } }), + { + id: 'association', + margin: { top: 35, right: 50, bottom: 40, left: 200 }, + interaction: { x_linked: true }, + }, + { + id: 'assoc2', + margin: { top: 35, right: 300, bottom: 40, left: 50 }, + interaction: { x_linked: true }, + }, ], }; - this.plot = populate('#plot', null, layout); + this.plot = populate('#plot', new DataSources(), layout); const panel0 = this.plot.layout.panels[0]; const panel1 = this.plot.layout.panels[1]; @@ -137,9 +144,12 @@ describe('LocusZoom.Plot', function() { describe('Mouse Guide Layer', function() { beforeEach(function() { d3.select('body').append('div').attr('id', 'plot'); - const layout = LAYOUTS.get('plot', 'standard_association'); - layout.state = { chr: '1', start: 1, end: 100000 }; - this.plot = populate('#plot', null, layout); + const layout = { + state: {}, + width: 800, + panels: [], + }; + this.plot = populate('#plot', new DataSources(), layout); }); it('first child should be a mouse guide layer group element', function() { diff --git a/test/unit/data/test_requester.js b/test/unit/data/test_requester.js index 17b5ac64..d267a846 100644 --- a/test/unit/data/test_requester.js +++ b/test/unit/data/test_requester.js @@ -19,6 +19,7 @@ describe('Requester object defines and parses requests', function () { ['assoc1', { name: 'assoc1', getData: () => Promise.resolve(1) }], ['someld', { name: 'someld', getData: () => Promise.resolve(2) }], ['assoc2', { name: 'assoc2' }], + ['catalog', { name: 'catalog' }], ]); this._requester = new Requester(this._all_datasources); }); @@ -55,6 +56,14 @@ describe('Requester object defines and parses requests', function () { this._requester.config_to_sources({ 'not:allowed': 'whatever' }, []); }, /Invalid namespace name: 'not:allowed'/); + assert.throws( + () => { + this._requester.config_to_sources({ 'somenamespace': 'nowhere' }, []); + }, + /not found in DataSources/, + 'Namespace references something not registered in datasource' + ); + // Test duplicate namespace errors: joins assert.throws(() => { this._requester.config_to_sources( @@ -65,6 +74,19 @@ describe('Requester object defines and parses requests', function () { ] ); }, /join name 'combined' must be unique/); + + assert.throws( + () => { + this._requester.config_to_sources( + {}, + [ + {name: 'combined', type: 'left_match', requires: ['unregistered', 'whatisthis']}, + ] + ); + }, + /cannot operate on unknown provider/ + ); + }); it('performs joins based on layout spec', function () { @@ -83,8 +105,73 @@ describe('Requester object defines and parses requests', function () { return this._requester.getData({}, entities, dependencies) .then((res) => { - assert.equal(res, 6); + assert.equal(res, 6); // 1 + 2 + 3 }); }); + + it('tries to auto-generate data_operations[@type=fetch] if not provided', function () { + const namespace_options = { assoc: 'assoc1', catalog: 'catalog', ld: 'someld' }; + let data_operations = []; // no operations, fetch or otherwise + + let [_, dependencies] = this._requester.config_to_sources(namespace_options, data_operations); + assert.deepEqual( + data_operations, + [{type: 'fetch', from: ['assoc', 'catalog', 'ld']}], // autogen doesn't specify dependencies, like ld(assoc) + 'Layout data_ops is mutated in place to reference namespaces (no dependencies assumed when auto-specifying)' + ); + + assert.deepEqual(dependencies, ['assoc', 'catalog', 'ld'], 'Dependencies are auto-guessed from namespaces'); + + // Related scenario: no fetch rule defined, but other rules are! + data_operations = [{ type: 'sumtwo', name: 'somejoin', requires: ['assoc', 'ld'], params: [5] }]; + ([_, dependencies] = this._requester.config_to_sources(namespace_options, data_operations)); + assert.deepEqual( + data_operations, + [ + { type: 'fetch', from: ['assoc', 'catalog', 'ld'] }, + { type: 'sumtwo', name: 'somejoin', requires: ['assoc', 'ld'], params: [5] }, + ], + 'Auto-generates fetch rules specifically; leaves other data ops untouched' + ); + assert.deepEqual(dependencies, ['assoc', 'catalog', 'ld', 'somejoin(assoc, ld)'], 'Dependencies are (still) auto-guessed from namespaces'); + }); + + it('attempts to reference all namespaces in data_operations[@type=fetch] at least once', function () { + const namespace_options = {'assoc': 'assoc1', 'catalog': 'catalog'}; + const data_operations = [{ type: 'fetch', from: ['assoc'] }]; // Fetch rules exist, but catalog not referenced! Eg, this could be someone creating a child layout; modifying a nested list is annoying + + const [_, dependencies] = this._requester.config_to_sources(namespace_options, data_operations); + assert.deepEqual( + data_operations, + [{type: 'fetch', from: ['assoc', 'catalog']}], + 'Layout data_ops is mutated in place to reference namespaces (no dependencies assumed when auto-specifying)' + ); + assert.deepEqual(dependencies, ['assoc', 'catalog'], 'Dependencies take all namespaces into account'); + }); + + it('autogenerates names for join operations, if none are provided', function () { + const namespace_options = { assoc: 'assoc1', catalog: 'catalog' }; + const data_operations = [ + { type: 'fetch', from: ['assoc', 'catalog'] }, + { type: 'sumtwo', name: 'has_name', requires: ['assoc', 'catalog'], params: [] }, + { type: 'sumtwo', requires: ['assoc', 'has_name'], params: [] }, + ]; + + const [_, dependencies] = this._requester.config_to_sources(namespace_options, data_operations); + assert.deepEqual( + data_operations, + [ + { type: 'fetch', from: ['assoc', 'catalog'] }, + { type: 'sumtwo', name: 'has_name', requires: ['assoc', 'catalog'], params: [] }, + { type: 'sumtwo', name: 'join0', requires: ['assoc', 'has_name'], params: [] }, + ], + 'Layout data_ops is mutated in place to give a name to any join without one' + ); + + assert.deepEqual( + dependencies, + ['assoc', 'catalog', 'has_name(assoc, catalog)', 'join0(assoc, has_name)'], + 'Dependencies reference the auto-generated join names correctly'); + }); }); }); diff --git a/test/unit/test_layouts.js b/test/unit/test_layouts.js index dd4dd806..90854235 100644 --- a/test/unit/test_layouts.js +++ b/test/unit/test_layouts.js @@ -1,8 +1,7 @@ import { assert } from 'chai'; import LAYOUTS, {_LayoutRegistry} from '../../esm/registry/layouts'; -import {deepCopy, merge} from '../../esm/helpers/layouts'; +import {applyNamespaces, deepCopy, merge} from '../../esm/helpers/layouts'; -import clone from 'just-clone'; describe('_LayoutRegistry', function() { describe('Provides a method to list current layouts by type', function() { @@ -125,141 +124,107 @@ describe('_LayoutRegistry', function() { }; lookup.add('test', 'test', base_layout); - assert.deepEqual(lookup.get('test', 'test', mods), expected_layout); + const actual = lookup.get('test', 'test', mods); + assert.deepEqual(actual, expected_layout); + + assert.notDeepEqual(actual, base_layout, 'Overriding the layout does not change the original'); }); - it.skip('Allows for namespacing arbitrary keys and values at all nesting levels', function() { - //FIXME: This test references a lot of beheavior that was deprecated/ removed in modern LZ + it('Allows for overriding namespaces', function() { const lookup = new _LayoutRegistry(); - const base_layout = { - scalar_0: 123, - '{{namespace}}scalar_1': 'aardvark', - 'dingo:scalar_2': '1:albacore', - namespace_scalar: '{{namespace}}badger', - namespace_0_scalar: '0:crocodile', - namespace_dingo_scalar: 'dingo:emu', - namespace_1_scalar: '1:ferret', - array_of_scalars: [ 4, 5, 6 ], - nested_object: { - property_0: { - scalar_0: 0, - scalar_1: 'grackle', - namespace_scalar: '{{{{namespace}}hog}} and {{1:yak}} and {{jackal:zebu}}', - namespace_0_scalar: '0:iguana', - namespace_jackal_scalar: 'jackal:kangaroo', - namespace_dingo_scalar: 'dingo:lemur', - namespace_1_scalar: '1:moose', - array: ['nematoad', 'oryx', '1:pigeon', 'jackal:quail'], - object: { - scalar: 'rhea', - array: ['serpent', '0:tortoise', 'upapa:vulture', '{{namespace}}xerus'], - }, + const layout = { + width: 400, + panels: [ + { + data_layers: [ + {id: 'assoc1', namespace: {assoc: 'assoc', ld: 'ld' }}, + {id: 'assoc2', namespace: {assoc: 'assoc', catalog: 'catalog' }}, + ], }, - property_1: false, - property_2: true, - }, + ], }; - - lookup.add('test', 'test', base_layout); - // Explicit directive to NOT apply namespaces: no changes - const unnamespaced_layout = lookup.get('test', 'test', { unnamespaced: true }); - assert.deepEqual(unnamespaced_layout, base_layout); - - // No defined namespaces: drop all namespaces - const no_namespace_layout = lookup.get('test', 'test'); - assert.equal(no_namespace_layout['scalar_1'], 'aardvark'); - assert.equal(no_namespace_layout['scalar_2'], 'albacore'); - assert.equal(no_namespace_layout.namespace_scalar, 'badger'); - assert.equal(no_namespace_layout.namespace_0_scalar, 'crocodile'); - assert.equal(no_namespace_layout.namespace_dingo_scalar, 'emu'); - assert.equal(no_namespace_layout.namespace_1_scalar, 'ferret'); - assert.equal(no_namespace_layout.nested_object.property_0.namespace_scalar, '{{hog}} and {{yak}} and {{zebu}}'); - assert.equal(no_namespace_layout.nested_object.property_0.namespace_0_scalar, 'iguana'); - assert.equal(no_namespace_layout.nested_object.property_0.namespace_jackal_scalar, 'kangaroo'); - assert.equal(no_namespace_layout.nested_object.property_0.namespace_dingo_scalar, 'lemur'); - assert.equal(no_namespace_layout.nested_object.property_0.namespace_1_scalar, 'moose'); - assert.equal(no_namespace_layout.nested_object.property_0.array[1], 'oryx'); - assert.equal(no_namespace_layout.nested_object.property_0.array[2], 'pigeon'); - assert.equal(no_namespace_layout.nested_object.property_0.array[3], 'quail'); - assert.equal(no_namespace_layout.nested_object.property_0.object.array[1], 'tortoise'); - assert.equal(no_namespace_layout.nested_object.property_0.object.array[2], 'vulture'); - assert.equal(no_namespace_layout.nested_object.property_0.object.array[3], 'xerus'); - - // Single namespace string: use in place of all namespace placeholders - const single_namespace_layout = lookup.get('test', 'test', { namespace: 'ns' }); - assert.equal(single_namespace_layout['ns:scalar_1'], 'aardvark'); - assert.equal(single_namespace_layout['ns:scalar_2'], 'ns:albacore'); - assert.equal(single_namespace_layout.namespace_scalar, 'ns:badger'); - assert.equal(single_namespace_layout.namespace_0_scalar, 'ns:crocodile'); - assert.equal(single_namespace_layout.namespace_dingo_scalar, 'ns:emu'); - assert.equal(single_namespace_layout.namespace_1_scalar, 'ns:ferret'); - assert.equal(single_namespace_layout.nested_object.property_0.namespace_scalar, '{{ns:hog}} and {{ns:yak}} and {{ns:zebu}}'); - assert.equal(single_namespace_layout.nested_object.property_0.namespace_0_scalar, 'ns:iguana'); - assert.equal(single_namespace_layout.nested_object.property_0.namespace_jackal_scalar, 'ns:kangaroo'); - assert.equal(single_namespace_layout.nested_object.property_0.namespace_dingo_scalar, 'ns:lemur'); - assert.equal(single_namespace_layout.nested_object.property_0.namespace_1_scalar, 'ns:moose'); - assert.equal(single_namespace_layout.nested_object.property_0.array[1], 'ns:oryx'); - assert.equal(single_namespace_layout.nested_object.property_0.array[2], 'ns:pigeon'); - assert.equal(single_namespace_layout.nested_object.property_0.array[3], 'ns:quail'); - assert.equal(single_namespace_layout.nested_object.property_0.object.array[1], 'ns:tortoise'); - assert.equal(single_namespace_layout.nested_object.property_0.object.array[2], 'ns:vulture'); - assert.equal(single_namespace_layout.nested_object.property_0.object.array[3], 'ns:xerus'); - - // Array of namespaces: replace number-indexed namespace holders, - // resolve {{namespace}} and any named namespaces to 0:. - const array_namespace_layout = lookup.get('test', 'test', { namespace: ['ns_0', 'ns_1'] }); - assert.equal(array_namespace_layout['ns_0:scalar_1'], 'aardvark'); - assert.equal(array_namespace_layout['ns_0:scalar_2'], 'ns_1:albacore'); - assert.equal(array_namespace_layout.namespace_scalar, 'ns_0:badger'); - assert.equal(array_namespace_layout.namespace_0_scalar, 'ns_0:crocodile'); - assert.equal(array_namespace_layout.namespace_dingo_scalar, 'ns_0:emu'); - assert.equal(array_namespace_layout.namespace_1_scalar, 'ns_1:ferret'); - assert.equal(array_namespace_layout.nested_object.property_0.namespace_scalar, '{{ns_0:hog}} and {{ns_1:yak}} and {{ns_0:zebu}}'); - assert.equal(array_namespace_layout.nested_object.property_0.namespace_0_scalar, 'ns_0:iguana'); - assert.equal(array_namespace_layout.nested_object.property_0.namespace_jackal_scalar, 'ns_0:kangaroo'); - assert.equal(array_namespace_layout.nested_object.property_0.namespace_dingo_scalar, 'ns_0:lemur'); - assert.equal(array_namespace_layout.nested_object.property_0.namespace_1_scalar, 'ns_1:moose'); - assert.equal(array_namespace_layout.nested_object.property_0.array[1], 'ns_0:oryx'); - assert.equal(array_namespace_layout.nested_object.property_0.array[2], 'ns_1:pigeon'); - assert.equal(array_namespace_layout.nested_object.property_0.array[3], 'ns_0:quail'); - assert.equal(array_namespace_layout.nested_object.property_0.object.array[1], 'ns_0:tortoise'); - assert.equal(array_namespace_layout.nested_object.property_0.object.array[2], 'ns_0:vulture'); - assert.equal(array_namespace_layout.nested_object.property_0.object.array[3], 'ns_0:xerus'); - // first defined key to any non-matching namespaces. - const object_namespace_layout = lookup.get('test', 'test', { - namespace: { - dingo: 'ns_dingo', - jackal: 'ns_jackal', - 1: 'ns_1', - 'default': 'ns_default', - }, + lookup.add('plot', 'testfixture', layout); + const modified = lookup.get('plot', 'testfixture', { + namespace: { assoc: 'assoc12', catalog: 'ebi_cat' }, + width: 800, // Add a property during overrides + new_prop: true, }); - assert.equal(object_namespace_layout['ns_default:scalar_1'], 'aardvark'); - assert.equal(object_namespace_layout['ns_dingo:scalar_2'], 'ns_1:albacore'); - assert.equal(object_namespace_layout.namespace_scalar, 'ns_default:badger'); - assert.equal(object_namespace_layout.namespace_0_scalar, 'ns_default:crocodile'); - assert.equal(object_namespace_layout.namespace_dingo_scalar, 'ns_dingo:emu'); - assert.equal(object_namespace_layout.namespace_1_scalar, 'ns_1:ferret'); - assert.equal(object_namespace_layout.nested_object.property_0.namespace_scalar, '{{ns_default:hog}} and {{ns_1:yak}} and {{ns_jackal:zebu}}'); - assert.equal(object_namespace_layout.nested_object.property_0.namespace_0_scalar, 'ns_default:iguana'); - assert.equal(object_namespace_layout.nested_object.property_0.namespace_jackal_scalar, 'ns_jackal:kangaroo'); - assert.equal(object_namespace_layout.nested_object.property_0.namespace_dingo_scalar, 'ns_dingo:lemur'); - assert.equal(object_namespace_layout.nested_object.property_0.namespace_1_scalar, 'ns_1:moose'); - assert.equal(object_namespace_layout.nested_object.property_0.array[1], 'ns_default:oryx'); - assert.equal(object_namespace_layout.nested_object.property_0.array[2], 'ns_1:pigeon'); - assert.equal(object_namespace_layout.nested_object.property_0.array[3], 'ns_jackal:quail'); - assert.equal(object_namespace_layout.nested_object.property_0.object.array[1], 'ns_default:tortoise'); - assert.equal(object_namespace_layout.nested_object.property_0.object.array[2], 'ns_default:vulture'); - assert.equal(object_namespace_layout.nested_object.property_0.object.array[3], 'ns_default:xerus'); - }); + const expected = { + width: 800, + new_prop: true, + panels: [ + { + data_layers: [ + {id: 'assoc1', namespace: {assoc: 'assoc12', ld: 'ld' }}, + {id: 'assoc2', namespace: {assoc: 'assoc12', catalog: 'ebi_cat' }}, + ], + }, + ], + }; - it.skip('Allows for inheriting namespaces', function() { - assert.ok(false, 'TODO: Reimplement with new namespace behavior'); + assert.deepEqual(modified, expected, 'Namespaces are applied to children as part of overrides'); }); }); }); describe('Layout helpers', function () { + describe('applyNamespaces', function () { + it('warns if layout or namespace-overrides are not objects', function () { + assert.throws( + () => applyNamespaces(null, {}), + /as objects/ + ); + assert.throws( + () => applyNamespaces({}, 42), + /as objects/ + ); + assert.ok(applyNamespaces({}), 'If no namespaced provided, default value is used'); + }); + + it('Can override namespaces referenced in both a layout and global object', function () { + const base = { + id: 'a_layer', + namespace: { assoc: 'assoc', ld: 'ld', catalog: 'catalog' }, + x_field: 'assoc:position', + y_field: 'ld:correlation', + }; + + const expected = { + id: 'a_layer', + namespace: { assoc: 'assoc22', ld: 'ld5', catalog: 'catalog' }, + x_field: 'assoc:position', + y_field: 'ld:correlation', + }; + + + const actual = applyNamespaces(base, { assoc: 'assoc22', ld: 'ld5', other_anno: 'mything' }); + assert.deepEqual(actual, expected, 'Overrides as appropriate'); + }); + + it('Can override namespaces in all child objects', function () { + const base = { + panels: [{ + data_layers: [ + { id: 'a', x_field: 'assoc:variant', namespace: { assoc: 'assoc', catalog: 'catalog' }}, + { id: 'b', x_field: 'someanno:afield', namespace: { catalog: 'catalog', ld: 'ld', other_thing: 'unused' }}, + ], + }], + }; + + const expected = { + panels: [{ + data_layers: [ + { id: 'a', x_field: 'assoc:variant', namespace: { assoc: 'myassoc', catalog: 'mycat' }}, + { id: 'b', x_field: 'someanno:afield', namespace: { catalog: 'mycat', ld: 'ld5', other_thing: 'unused' }}, + ], + }], + }; + + const actual = applyNamespaces(base, { assoc: 'myassoc', ld: 'ld5', catalog: 'mycat' }); + assert.deepEqual(actual, expected, 'Selectively applies overrides to nested objects'); + }); + }); + describe('Provides a method to merge layout objects', function() { beforeEach(function() { this.default_layout = { @@ -364,13 +329,11 @@ describe('Layout helpers', function () { it('can mutate a set of values based on a jsonpath selector', function () { const base_panel = LAYOUTS.get('panel', 'association'); - const base_layer = LAYOUTS.get('data_layer', 'association_pvalues'); - const scenarios = [ ['set single value to a constant', '$.panels[?(@.tag === "association")].id', 'one', ['one']], ['toggle a boolean false to true', '$.fake_field', true, [true]], ['set many values to a constant', '$..id', 'all', ['all', 'all', 'all', 'all', 'all', 'all']], - ['add items to an array', '$..data_layers[?(@.tag === "association")].fields', (old_value) => old_value.concat(['field1', 'field2']), [base_layer.fields.concat(['field1', 'field2'])]], + ['add items to an array', '$.some_list', (old_value) => old_value.concat(['field1', 'field2']), [['field0', 'field1', 'field2']]], // Two subtly different cases for nested objects (direct query, and as part of filtered list) ['mutate an object inside an object', '$..panels[?(@.tag === "association")].margin', (old_config) => (old_config.new_field = 10) && old_config, [{bottom: 40, left: 50, right: 50, top: 35, new_field: 10}]], ['mutate an object inside a list', '$..panels[?(@.tag === "association")]', (old_config) => (old_config.margin.new_field = 10) && old_config, [Object.assign(base_panel, {margin: {bottom: 40, left: 50, right: 50, top: 35, new_field: 10}})]], @@ -379,6 +342,7 @@ describe('Layout helpers', function () { for (let [label, selector, mutator, expected] of scenarios) { const base = LAYOUTS.get('plot', 'standard_association'); base.fake_field = false; + base.some_list = ['field0']; assert.deepEqual(LAYOUTS.mutate_attrs(base, selector, mutator), expected, `Scenario '${label}' passed`); } }); From d49f0d3bbf45d044ee4c39a7b632e114595aa111 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 15 Sep 2021 14:40:10 -0400 Subject: [PATCH 021/100] All predefined layer layouts should have a special property called "auto_fields", to help users know what data is needed. --- esm/helpers/layouts.js | 5 ++--- esm/registry/layouts.js | 9 ++++++++- test/unit/test_layouts.js | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/esm/helpers/layouts.js b/esm/helpers/layouts.js index e300c322..fc87bca9 100644 --- a/esm/helpers/layouts.js +++ b/esm/helpers/layouts.js @@ -128,10 +128,11 @@ function nameToSymbol(shape) { * @return {Set} */ function findFields(layout, prefixes, field_finder = null) { + const fields = new Set(); if (!field_finder) { if (!prefixes.length) { // A layer that doesn't ask for external data does not need to check if the provider returns expected fields - return []; + return fields; } const all_ns = prefixes.join('|'); @@ -140,8 +141,6 @@ function findFields(layout, prefixes, field_finder = null) { field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\w+)`, 'g'); } - const fields = new Set(); - for (const value of Object.values(layout)) { const value_type = typeof value; let matches; diff --git a/esm/registry/layouts.js b/esm/registry/layouts.js index 464b5fd6..37676517 100644 --- a/esm/registry/layouts.js +++ b/esm/registry/layouts.js @@ -1,5 +1,5 @@ import {RegistryBase} from './base'; -import {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField} from '../helpers/layouts'; +import {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts'; import * as layouts from '../layouts'; /** @@ -60,6 +60,13 @@ class LayoutRegistry extends RegistryBase { } // Ensure that each use of a layout can be modified, by returning a copy is independent const copy = deepCopy(item); + + // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested + // from external sources. This is purely a hint, because not every layout is generated through the registry. + if (type === 'data_layer' && copy.namespace) { + copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))]; + } + return super.get(type).add(name, copy, override); } diff --git a/test/unit/test_layouts.js b/test/unit/test_layouts.js index 90854235..28995391 100644 --- a/test/unit/test_layouts.js +++ b/test/unit/test_layouts.js @@ -47,6 +47,27 @@ describe('_LayoutRegistry', function() { to_add.id = 2; assert.deepEqual(found.id, 1, 'Registry stores a copy: mutating the source object later does not propagate changes'); }); + + it('annotates every new registry item with a best guess of what fields are requested from external providers', function() { + const lookup = new _LayoutRegistry(); + + const base_panel = { id: 'a_panel', red_herring: 'looks_like:a_field' }; + lookup.add('panel', 'not_modified', base_panel); + let actual = lookup.get('panel', 'not_modified'); + assert.deepEqual(actual, base_panel, 'Panel layouts are unchanged'); + + const base_layer = { + id: 'a_layer', + namespace: { assoc: 'assoc', ld: 'ld' }, + x_field: 'assoc:something', + y_field: 'ld:correlation', + red_herring: 'unknown_ns:means_not_field', + label: 'chromosome {{chr}}', + }; + lookup.add('data_layer', 'adds_autofields', base_layer); + actual = lookup.get('data_layer', 'adds_autofields'); + assert.deepEqual(actual._auto_fields, ['assoc:something', 'ld:correlation'], 'Data layers in registry receive an added magic property called _auto_fields'); + }); }); // get() From 80aff29b3b1ea40e0988bc360a839794de69c3ea Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 15 Sep 2021 15:21:07 -0400 Subject: [PATCH 022/100] Allow most common adapters to restrict what fields are returned, eg if they're concerned about memory usage. By default we use this only for the most common "stock" reusable datasets, like LD, where the server returns a lot of redundant info and almost no one is writing their own LD adapter. We don't use it for builtin adapters like Assoc, because people customize assoc layouts really often and they get confused if their custom data starts disappearing by magic. --- esm/data/adapters.js | 35 +++++++++++++++++++++++++++++---- esm/layouts/index.js | 2 +- esm/registry/layouts.js | 2 +- test/unit/data/test_adapters.js | 8 ++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index eb42dbed..3852ffe3 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -68,8 +68,9 @@ class BaseLZAdapter extends BaseUrlAdapter { // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear // in the response. (gene_name instead of genes.gene_name) - const {prefix_namespace} = config; - this._prefix_namespace = (typeof prefix_namespace === 'boolean') ? prefix_namespace : true; + const { prefix_namespace = true, limit_fields } = config; + this._prefix_namespace = prefix_namespace; + this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields } _getCacheKey(options) { @@ -102,11 +103,16 @@ class BaseLZAdapter extends BaseUrlAdapter { // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for // assoc data might see "variant" as "assoc.variant" + const { _limit_fields } = this; + const { _provider_name } = options; + return records.map((row) => { return Object.entries(row).reduce( (acc, [label, value]) => { - // Rename API fields to format `namespace:fieldname` - acc[`${options._provider_name}:${label}`] = value; + // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload. + if (!_limit_fields || _limit_fields.has(label)) { + acc[`${_provider_name}:${label}`] = value; + } return acc; }, {} @@ -222,6 +228,13 @@ class AssociationLZ extends BaseUMAdapter { * @see module:LocusZoom_Adapters~BaseUMAdapter */ class GwasCatalogLZ extends BaseUMAdapter { + constructor(config) { + if (!config.limit_fields) { + config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant']; + } + super(config); + } + /** * @param {string} config.url The base URL for the remote data. * @param {Object} config.params @@ -370,6 +383,13 @@ class GeneConstraintLZ extends BaseLZAdapter { class LDServer extends BaseUMAdapter { + constructor(config) { + if (!config.limit_fields) { + config.limit_fields = ['variant2', 'position2', 'correlation']; + } + super(config); + } + __find_ld_refvar(state, assoc_data) { const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant'); const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue'); @@ -517,6 +537,13 @@ class LDServer extends BaseUMAdapter { * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37. */ class RecombLZ extends BaseUMAdapter { + constructor(config) { + if (!config.limit_fields) { + config.limit_fields = ['position', 'recomb_rate']; + } + super(config); + } + /** * Add query parameters to the URL to construct a query for the specified region */ diff --git a/esm/layouts/index.js b/esm/layouts/index.js index e7404615..b347bb79 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -467,7 +467,7 @@ const genes_layer_filtered = merge({ const annotation_catalog_layer = { // Identify GWAS hits that are present in the GWAS catalog namespace: { 'assoc': 'assoc', 'catalog': 'catalog' }, - join_options: [{ + data_operations: [{ type: 'assoc_to_gwas_catalog', name: 'combined', requires: ['assoc', 'catalog'], diff --git a/esm/registry/layouts.js b/esm/registry/layouts.js index 37676517..6064b18d 100644 --- a/esm/registry/layouts.js +++ b/esm/registry/layouts.js @@ -64,7 +64,7 @@ class LayoutRegistry extends RegistryBase { // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested // from external sources. This is purely a hint, because not every layout is generated through the registry. if (type === 'data_layer' && copy.namespace) { - copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))]; + copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort(); } return super.get(type).add(name, copy, override); diff --git a/test/unit/data/test_adapters.js b/test/unit/data/test_adapters.js index ce0bc71b..99d9698d 100644 --- a/test/unit/data/test_adapters.js +++ b/test/unit/data/test_adapters.js @@ -65,6 +65,14 @@ describe('Data adapters', function () { ).then((result) => assert.deepEqual(result, [{'sometest:a': 1, 'sometest:b': 2}])); }); + it('can optionally restrict final payload to a preset list of fields', function () { + const source = new BaseTestClass({ limit_fields: ['b'] }); + return source.getData({ + _provider_name: 'sometest', + _test_data: [{ a: 1, b: 2 }]} + ).then((result) => assert.deepEqual(result, [{ 'sometest:b': 2}])); + }); + it('has a helper to locate dependent fields that were already namespaced', function () { const source = new BaseTestClass({}); const match = source._findPrefixedKey({'sometest:aaa': 1, 'sometest:a': 2, 'sometest:aa': 3}, 'a'); From d4820637b6810f7e59b10aa41945b6886bb9fd7c Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 15 Sep 2021 15:26:06 -0400 Subject: [PATCH 023/100] Appease linters --- esm/data/requester.js | 4 ++-- test/unit/components/test_datalayer.js | 2 +- test/unit/components/test_panel.js | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/esm/data/requester.js b/esm/data/requester.js index d449db5b..b4f49968 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -46,7 +46,7 @@ class Requester { * removed adapter. * @param {Object} namespace_options * @param {Array} data_operations - * @returns {(Map|*[])[]} + * @returns {Array} Map of entities and list of dependencies */ config_to_sources(namespace_options = {}, data_operations = []) { const entities = new Map(); @@ -122,7 +122,7 @@ class Requester { * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances * (things that implement a method getData). * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order - * @returns {Promise<*[]>|*} + * @returns {Promise} */ getData(state, entities, dependencies) { if (!dependencies.length) { diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index fd4670ab..cba10703 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -944,7 +944,7 @@ describe('LocusZoom.DataLayer', function () { this.plot = null; const data_sources = new DataSources() .add('d', ['StaticJSON', { - data: [{ id: 1, a: 12 }, { id: 2, a: 11 }, { id: 3, a: 13 }, { id: 4, a: 15 }, { id: 5, a: 14 }] + data: [{ id: 1, a: 12 }, { id: 2, a: 11 }, { id: 3, a: 13 }, { id: 4, a: 15 }, { id: 5, a: 14 }], }]); const layout = { panels: [ diff --git a/test/unit/components/test_panel.js b/test/unit/components/test_panel.js index 11702662..a88eec57 100644 --- a/test/unit/components/test_panel.js +++ b/test/unit/components/test_panel.js @@ -2,7 +2,6 @@ import { assert } from 'chai'; import * as d3 from 'd3'; import sinon from 'sinon'; -import {LAYOUTS} from '../../../esm/registry'; import {populate} from '../../../esm/helpers/display'; import Plot from '../../../esm/components/plot'; import Panel from '../../../esm/components/panel'; From 9a4b9de5257c14004c22639e41099aa4e0e4b08d Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 15 Sep 2021 18:28:35 -0400 Subject: [PATCH 024/100] Add dev friendly message and reference published dependency --- esm/components/data_layer/base.js | 6 ++++-- package-lock.json | 22 ++++++++++++++++++++++ package.json | 3 ++- test/unit/components/test_datalayer.js | 2 +- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index c3beb9e5..a56bab57 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -498,9 +498,11 @@ class BaseDataLayer { } } if (fields_unseen.size) { - // Current implementation is a soft warning, so that certain "incremental enhancement" features (like rsIDs in tooltips) can fail gracefully is the API does not provide the requested info - // This basically exists because of the `{{#if fieldname}}` statement in template string syntax. + // Current implementation is a soft warning, so that certain "incremental enhancement" features + // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info. + // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data. console.warn(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}`); + console.warn('Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations"'); } } diff --git a/package-lock.json b/package-lock.json index 3fc639de..2880be84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1150,6 +1150,19 @@ } } }, + "@hapi/hoek": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", + "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==" + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -6258,6 +6271,15 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, + "undercomplicate": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/undercomplicate/-/undercomplicate-0.1.0.tgz", + "integrity": "sha512-kFQwB6Fhp0muZX2hNTwk5/5eyV9T29npTxAn3NHBsjG5xRzvn1OKm0RspEIJv8fjOOdMbawi93qKe/pom14iGQ==", + "requires": { + "@hapi/topo": "^5.1.0", + "just-clone": "^3.2.1" + } + }, "underscore": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", diff --git a/package.json b/package.json index fe93b1f1..ad251783 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "dependencies": { "d3": "^5.16.0", "deepmerge": "^4.2.2", - "just-clone": "^3.2.1" + "just-clone": "^3.2.1", + "undercomplicate": "^0.1.0" }, "devDependencies": { "@babel/core": "^7.13.1", diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index cba10703..54ad55aa 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -27,7 +27,7 @@ describe('LocusZoom.DataLayer', function () { let spy = sinon.spy(console, 'warn'); this.layer.data = [{ 'assoc:variant': '1:23_A/B', 'assoc:position': 23 }]; this.layer.applyDataMethods(); - assert.ok(spy.calledOnce, 'Console.warn was called with data contract errors'); + assert.ok(spy.calledTwice, 'Console.warn was called with data contract errors'); assert.ok(spy.firstCall.args[0].match(/Missing fields are: assoc:rsid/), 'Developer message identifies the missing fields'); }); From abb04efc29154515d9e0f0d772d9e8875174cf8a Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 15 Sep 2021 18:43:10 -0400 Subject: [PATCH 025/100] Replace string.matchAll, as it's not supported in older browsers yet (caught by node10 as canary in coalmine) --- esm/helpers/layouts.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esm/helpers/layouts.js b/esm/helpers/layouts.js index fc87bca9..eb61b5be 100644 --- a/esm/helpers/layouts.js +++ b/esm/helpers/layouts.js @@ -143,9 +143,12 @@ function findFields(layout, prefixes, field_finder = null) { for (const value of Object.values(layout)) { const value_type = typeof value; - let matches; + let matches = []; if (value_type === 'string') { - matches = [...value.matchAll(field_finder)].map((m) => m[1]); + let a_match; + while ((a_match = field_finder.exec(value)) !== null) { + matches.push(a_match[1]); + } } else if (value !== null && value_type === 'object') { matches = findFields(value, prefixes, field_finder); } else { From d57542edeb8d6c58f4206069d5b95ec4a4ac0a16 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Fri, 17 Sep 2021 15:24:41 -0400 Subject: [PATCH 026/100] Revive aggtest demo to new adapter structure; replace connector source with a join op - Expose JOINS to UMD - Minor readability and style updates to existing adapters - All default layouts should provide explicit names for joins to support extensibility Agg tests are not an officially used demo; this is an update to code but does not represent 100% of newest best practices. --- esm/data/adapters.js | 10 +- esm/ext/lz-aggregation-tests.js | 248 +++---- esm/index.js | 4 +- esm/layouts/index.js | 1 + examples/aggregation_tests.html | 2 +- examples/js/aggregation-tests-example-page.js | 95 ++- package-lock.json | 655 +++++++++++++++--- package.json | 1 + test/unit/components/test_plot.js | 2 +- 9 files changed, 707 insertions(+), 311 deletions(-) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 3852ffe3..7f500e8e 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -322,8 +322,8 @@ class GeneConstraintLZ extends BaseLZAdapter { this._prefix_namespace = false; } - _buildRequestOptions(options, genes_data) { - const build = options.genome_build || this._config.build; + _buildRequestOptions(state, genes_data) { + const build = state.genome_build || this._config.build; if (!build) { throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`); } @@ -335,14 +335,14 @@ class GeneConstraintLZ extends BaseLZAdapter { unique_gene_names.add(gene.gene_name); } - options.query = [...unique_gene_names.values()].map(function (gene_name) { + state.query = [...unique_gene_names.values()].map(function (gene_name) { // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268 const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // Each gene symbol is a separate graphQL query, grouped into one request using aliases return `${alias}: gene(gene_symbol: "${gene_name}", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `; }); - options.build = build; - return Object.assign({}, options); + state.build = build; + return Object.assign({}, state); } _performRequest(options) { diff --git a/esm/ext/lz-aggregation-tests.js b/esm/ext/lz-aggregation-tests.js index f4c2bb71..2b53238c 100644 --- a/esm/ext/lz-aggregation-tests.js +++ b/esm/ext/lz-aggregation-tests.js @@ -34,9 +34,7 @@ import {helpers} from 'raremetal.js'; function install (LocusZoom) { - const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter'); - const BaseApiAdapter = LocusZoom.Adapters.get('BaseApiAdapter'); - const ConnectorSource = LocusZoom.Adapters.get('ConnectorSource'); + const BaseUrlAdapter = LocusZoom.Adapters.get('BaseLZAdapter'); /** * Calculates gene or region-based tests based on provided data, using the raremetal.js library. @@ -46,71 +44,82 @@ function install (LocusZoom) { * @see module:ext/lz-aggregation-tests * @private */ - class AggregationTestSourceLZ extends BaseApiAdapter { - getURL(state, chain, fields) { + class AggregationTestSourceLZ extends BaseUrlAdapter { + constructor(config) { + config.prefix_namespace = false; + super(config); + } + + _buildRequestOptions(state) { + const { aggregation_tests = {} } = state; + const { + // eslint-disable-next-line no-unused-vars + genoset_id = null, genoset_build = null, phenoset_build = null, pheno = null, calcs = {}, masks = [], + } = aggregation_tests; + + aggregation_tests.mask_ids = masks.map((item) => item.name); + // Many of these params will be undefined if no tests defined + state.aggregation_tests = aggregation_tests; + return state; + } + + _getURL(state, chain, fields) { // Unlike most sources, calculations may require access to plot state data even after the initial request // This example source REQUIRES that the external UI widget would store the needed test definitions in a plot state // field called `aggregation_tests` (an object {masks: [], calcs: {}) - const required_info = state.aggregation_tests || {}; - - if (!chain.header) { - chain.header = {}; - } - // All of these fields are required in order to use this datasource. TODO: Add validation? - chain.header.aggregation_genoset_id = required_info.genoset_id || null; // Number - chain.header.aggregation_genoset_build = required_info.genoset_build || null; // String - chain.header.aggregation_phenoset_id = required_info.phenoset_id || null; // Number - chain.header.aggregation_pheno = required_info.pheno || null; // String - chain.header.aggregation_calcs = required_info.calcs || {}; // String[] - const mask_data = required_info.masks || []; - chain.header.aggregation_masks = mask_data; // {name:desc}[] - chain.header.aggregation_mask_ids = mask_data.map(function (item) { - return item.name; - }); // Number[] - return this.url; + return this._url; } - getCacheKey(state, chain, fields) { - this.getURL(state, chain, fields); // TODO: This just sets the chain.header fields + _getCacheKey(options) { + const { chr, start, end, aggregation_tests } = options; + const { genoset_id = null, genoset_build = null, phenoset_id = null, pheno = null, mask_ids } = aggregation_tests; + return JSON.stringify({ - chrom: state.chr, - start: state.start, - stop: state.end, - genotypeDataset: chain.header.aggregation_genoset_id, - phenotypeDataset: chain.header.aggregation_phenoset_id, - phenotype: chain.header.aggregation_pheno, + chrom: chr, + start: start, + stop: end, + genotypeDataset: genoset_id, + phenotypeDataset: phenoset_id, + phenotype: pheno, samples: 'ALL', - genomeBuild: chain.header.aggregation_genoset_build, - masks: chain.header.aggregation_mask_ids, + genomeBuild: genoset_build, + masks: mask_ids, }); } - fetchRequest(state, chain, fields) { - const url = this.getURL(state, chain, fields); - const body = this.getCacheKey(state, chain, fields); + _performRequest(options) { + const url = this._getURL(options); + const body = this._getCacheKey(options); // cache key doubles as request body const headers = { 'Content-Type': 'application/json', }; - return fetch(url, {method: 'POST', body: body, headers: headers}).then((response) => { - if (!response.ok) { - throw new Error(response.statusText); - } - return response.text(); - }).then(function (resp) { - const json = typeof resp == 'string' ? JSON.parse(resp) : resp; - if (json.error) { - // RAREMETAL-server quirk: The API sometimes returns a 200 status code for failed requests, - // with a human-readable error description as a key - // For now, this should be treated strictly as an error - throw new Error(json.error); - } - return json; - }); + return fetch(url, {method: 'POST', body: body, headers: headers}) + .then((response) => { + if (!response.ok) { + throw new Error(response.statusText); + } + return response.text(); + }).then((resp) => { + const json = typeof resp == 'string' ? JSON.parse(resp) : resp; + if (json.error) { + // RAREMETAL-server quirk: The API sometimes returns a 200 status code for failed requests, + // with a human-readable error description as a key + // For now, this should be treated strictly as an error + throw new Error(json.error); + } + return json.data; + }); } - annotateData(records, chain) { - // Operate on the calculated results. The result of this method will be added to chain.discrete + _annotateRecords(records, options) { + // The server response gives covariance for a set of masks, but the actual calculations are done separately. + // Eg, several types of aggtests might use the same covariance matrix. + + // TODO In practice, the test calculations are slow. Investigate caching full results (returning all from performRequest), not just covmat. + // This code is largely for demonstration purposes and does not reflect modern best practices possible in LocusZoom (eg sources + dependencies to link together requests) + const { aggregation_tests } = options; + const { calcs = [], mask_ids = [], masks = [] } = aggregation_tests; // In a page using live API data, the UI would only request the masks it needs from the API. // But in our demos, sometimes boilerplate JSON has more masks than the UI asked for. Limit what calcs we run (by @@ -122,19 +131,16 @@ function install (LocusZoom) { return { groups: [], variants: [] }; } - records.groups = records.groups.filter(function (item) { - return item.groupType === 'GENE'; - }); + records.groups = records.groups.filter((item) => item.groupType === 'GENE'); const parsed = helpers.parsePortalJSON(records); let groups = parsed[0]; const variants = parsed[1]; // Some APIs may return more data than we want (eg simple sites that are just serving up premade scorecov json files). // Filter the response to just what the user has chosen to analyze. - groups = groups.byMask(chain.header.aggregation_mask_ids); + groups = groups.byMask(mask_ids); // Determine what calculations to run - const calcs = chain.header.aggregation_calcs; if (!calcs || Object.keys(calcs).length === 0) { // If no calcs have been requested, then return a dummy placeholder immediately return { variants: [], groups: [], results: [] }; @@ -145,11 +151,11 @@ function install (LocusZoom) { .then(function (res) { // Internally, raremetal helpers track how the calculation is done, but not any display-friendly values // We will annotate each mask name (id) with a human-friendly description for later use - const mask_id_to_desc = chain.header.aggregation_masks.reduce(function (acc, val) { + const mask_id_to_desc = masks.reduce((acc, val) => { acc[val.name] = val.description; return acc; }, {}); - res.data.groups.forEach(function (group) { + res.data.groups.forEach((group) => { group.mask_name = mask_id_to_desc[group.mask]; }); return res.data; @@ -159,18 +165,6 @@ function install (LocusZoom) { throw new Error('Failed to calculate aggregation test results'); }); } - - normalizeResponse(data) { - return data; - } - - combineChainBody(records, chain) { - // aggregation tests are a bit unique, in that the data is rarely used directly- instead it is used to annotate many - // other layers in different ways. The calculated result has been added to `chain.discrete`, but will not be returned - // as part of the response body built up by the chain - return chain.body; - } - } /** @@ -180,40 +174,35 @@ function install (LocusZoom) { * @see module:LocusZoom_Adapters * @private */ - class AssocFromAggregationLZ extends BaseAdapter { - constructor(config) { - if (!config || !config.from) { - throw 'Must specify the name of the source that contains association data'; + class AssocFromAggregationLZ extends BaseUrlAdapter { + _buildRequestOptions(state, agg_results) { + // This adapter just reformats an existing payload from cache (maybe this is better as a data_operation instead of an adapter nowadays?) + if (!agg_results) { + throw new Error('Aggregation test results must be provided'); } - super(...arguments); - } - parseInit(config) { - super.parseInit(config); - this._from = config.from; + state._agg_results = agg_results; + return state; } - getRequest(state, chain, fields) { - // Does not actually make a request. Just pick off the specific bundle of data from a known payload structure. - if (chain.discrete && !chain.discrete[this._from]) { - throw `${this.constructor.SOURCE_NAME} cannot be used before loading required data for: ${this._from}`; - } - // Copy the data so that mutations (like sorting) don't affect the original - return Promise.resolve(JSON.parse(JSON.stringify(chain.discrete[this._from]['variants']))); + _performRequest(options) { + return Promise.resolve(options._agg_results['variants']); } - normalizeResponse(data) { + _normalizeResponse(data) { // The payload structure of the association source is slightly different than the one required by association // plots. For example, we need to parse variant names and convert to log_pvalue const REGEX_EPACTS = new RegExp('(?:chr)?(.+):(\\d+)_?(\\w+)?/?([^_]+)?_?(.*)?'); // match API variant strings - return data.map((one_variant) => { - const match = one_variant.variant.match(REGEX_EPACTS); + return data.map((item) => { + const { variant, altFreq, pvalue } = item; + const match = variant.match(REGEX_EPACTS); + const [_, chromosome, position, ref_allele] = match; return { - variant: one_variant.variant, - chromosome: match[1], - position: +match[2], - ref_allele: match[3], - ref_allele_freq: 1 - one_variant.altFreq, - log_pvalue: -Math.log10(one_variant.pvalue), + variant: variant, + chromosome, + position: +position, + ref_allele, + ref_allele_freq: 1 - altFreq, + log_pvalue: -Math.log10(pvalue), }; }).sort((a, b) => { a = a.variant; @@ -230,57 +219,32 @@ function install (LocusZoom) { } } - /** - * A sample connector that aligns calculated aggregation test data with corresponding gene information. Returns a body - * suitable for use with the genes datalayer. - * - * To use this source, one must specify a fields array that calls first the genes source, then a dummy field from - * this source. The output will be to transparently add several new fields to the genes data. - * @see module:ext/lz-aggregation-tests - * @see module:LocusZoom_Adapters - * @private - */ - class GeneAggregationConnectorLZ extends ConnectorSource { - _getRequiredSources() { - return ['gene_ns', 'aggregation_ns']; - } - - combineChainBody(data, chain) { - // The genes layer receives all results, and displays only the best pvalue for each gene - - // Tie the calculated group-test results to genes with a matching name - const aggregation_source_id = this._source_name_mapping['aggregation_ns']; - const gene_source_id = this._source_name_mapping['gene_ns']; - // This connector assumes that genes are the main body of records from the chain, and that aggregation tests are - // a standalone source that has not acted on genes data yet - const aggregationData = chain.discrete[aggregation_source_id]; - const genesData = chain.discrete[gene_source_id]; + LocusZoom.JoinFunctions.add('gene_plus_aggregation', (genes_data, aggregation_data) => { + // Used to highlight genes with significant aggtest results. Unlike a basic left join, it chooses one specific aggtest with the most significant results - const groupedAggregation = {}; // Group together all tests done on that gene- any mask, any test - - aggregationData.groups.forEach(function (result) { - if (!Object.prototype.hasOwnProperty.call(groupedAggregation, result.group)) { - groupedAggregation[result.group] = []; - } - groupedAggregation[result.group].push(result.pvalue); - }); - - // Annotate any genes that have test results - genesData.forEach(function (gene) { - const gene_id = gene.gene_name; - const tests = groupedAggregation[gene_id]; - if (tests) { - gene.aggregation_best_pvalue = Math.min.apply(null, tests); - } - }); - return genesData; - } - } + // Tie the calculated group-test results to genes with a matching name + const groupedAggregation = {}; // Group together all tests done on that gene- any mask, any test + aggregation_data.groups.forEach(function (result) { + if (!Object.prototype.hasOwnProperty.call(groupedAggregation, result.group)) { + groupedAggregation[result.group] = []; + } + groupedAggregation[result.group].push(result.pvalue); + }); + + // Annotate any genes that have test results + genes_data.forEach((gene) => { + const gene_id = gene.gene_name; + const tests = groupedAggregation[gene_id]; + if (tests) { + gene.aggregation_best_pvalue = Math.min.apply(null, tests); + } + }); + return genes_data; + }); LocusZoom.Adapters.add('AggregationTestSourceLZ', AggregationTestSourceLZ); LocusZoom.Adapters.add('AssocFromAggregationLZ', AssocFromAggregationLZ); - LocusZoom.Adapters.add('GeneAggregationConnectorLZ', GeneAggregationConnectorLZ); } diff --git a/esm/index.js b/esm/index.js index 12019b0f..1b02086e 100644 --- a/esm/index.js +++ b/esm/index.js @@ -14,11 +14,12 @@ import { populate } from './helpers/display'; import { ADAPTERS as Adapters, DATA_LAYERS as DataLayers, - WIDGETS as Widgets, + JOINS as JoinFunctions, LAYOUTS as Layouts, MATCHERS as MatchFunctions, SCALABLE as ScaleFunctions, TRANSFORMS as TransformationFunctions, + WIDGETS as Widgets, } from './registry'; @@ -30,6 +31,7 @@ const LocusZoom = { // Registries for plugin system Adapters, DataLayers, + JoinFunctions, Layouts, MatchFunctions, ScaleFunctions, diff --git a/esm/layouts/index.js b/esm/layouts/index.js index b347bb79..e68be594 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -410,6 +410,7 @@ const genes_layer = { from: ['gene', 'constraint(gene)'], }, { + name: 'gene_constraint', type: 'genes_to_gnomad_constraint', requires: ['gene', 'constraint'], }, diff --git a/examples/aggregation_tests.html b/examples/aggregation_tests.html index eb577622..68bae47e 100644 --- a/examples/aggregation_tests.html +++ b/examples/aggregation_tests.html @@ -231,7 +231,7 @@

References

window.aggregationBuilder.$on('run', function (options) { // One time only: Create the plot and table widgets if they don't already exist - if (!window.plot || !(window.plot instanceof LocusZoom.Plot)) { + if (!window.plot) { $("#section-results").css("display", "inherit"); // Hide results section until first use createDisplayWidgets(window.selected_group); // Hack: func binds widget references to global scope setupWidgetListeners( diff --git a/examples/js/aggregation-tests-example-page.js b/examples/js/aggregation-tests-example-page.js index a1458c78..f8b2f6fc 100644 --- a/examples/js/aggregation-tests-example-page.js +++ b/examples/js/aggregation-tests-example-page.js @@ -40,12 +40,17 @@ function customizePlotLayout(layout) { // 2. Genes layer must pull from the aggregation source + the aggregation_genes connector if we want to color // the gene track by aggregation test results const assocLayout = layout.panels[0].data_layers[2]; - assocLayout.fields.unshift('aggregation: all'); + assocLayout.namespace['aggregation'] = 'aggregation'; + assocLayout.data_operations[0].from = ['aggregation', 'assoc(aggregation)', 'ld(assoc)']; const genesLayout = layout.panels[1].data_layers[0]; genesLayout.namespace['aggregation'] = 'aggregation'; - genesLayout.namespace['aggregation_genes'] = 'aggregation_genes'; - genesLayout.fields.push('aggregation:all', 'aggregation_genes:all'); + genesLayout.namespace['aggregation'] = 'aggregation'; + genesLayout.data_operations.push({ + type: 'gene_plus_aggregation', + requires: ['gene_constraint', 'aggregation'], + }); + const colorConfig = [ { scale_function: 'if', @@ -199,12 +204,6 @@ function createDisplayWidgets(label_store, context) { source: '1000G', build: 'GRCh37', population: 'ALL', }]) .add('gene', ['GeneLZ', { url: apiBase + 'annotation/genes/', build: 'GRCh37' }]) - .add('aggregation_genes', ['GeneAggregationConnectorLZ', { - sources: { - aggregation_ns: 'aggregation', - gene_ns: 'gene', - }, - }]) .add('recomb', ['RecombLZ', { url: apiBase + 'annotation/recomb/results/', build: 'GRCh37' }]) .add('constraint', ['GeneConstraintLZ', { url: 'https://gnomad.broadinstitute.org/api/', @@ -327,45 +326,45 @@ function setupWidgetListeners(plot, aggregationTable, variantsTable, resultStora } }.bind(this)); - plot.subscribeToData( - ['aggregation:all', 'gene:all'], - function (data) { - // chain.discrete provides distinct data from each source - const gene_source_data = data.gene; - const agg_source_data = data.aggregation; - - const results = agg_source_data.results; - - // Aggregation calcs return very complex data. Parse it here, once, into reusable helper objects. - const parsed = raremetal.helpers.parsePortalJSON(agg_source_data); - const groups = parsed[0]; - const variants = parsed[1]; - - ///////// - // Post-process this data with any annotations required by data tables on this page - - // The aggregation results use the unique ENSEMBL ID for a gene. The gene source tells us how to connect - // that to a human-friendly gene name (as displayed in the LZ plot) - const _genes_lookup = {}; - gene_source_data.forEach(function (gene) { - const gene_id = gene.gene_id.split('.')[0]; // Ignore ensembl version on gene ids - _genes_lookup[gene_id] = gene.gene_name; - }); - groups.data.forEach(function (one_result) { - const this_group = groups.getOne(one_result.mask, one_result.group); - // Add synthetic fields that are not part of the raw calculation results - one_result.group_display_name = _genes_lookup[one_result.group] || one_result.group; - one_result.variant_count = this_group.variants.length; - }); - - // When new data has been received (and post-processed), pass it on to any UI elements that use that data - resultStorage({ - groups: groups, - variants: variants, - }); - }, - { discrete: true } - ); + // plot.subscribeToData( + // ['aggregation:all', 'gene:all'], + // function (data) { + // // chain.discrete provides distinct data from each source + // const gene_source_data = data.gene; + // const agg_source_data = data.aggregation; + // + // const results = agg_source_data.results; + // + // // Aggregation calcs return very complex data. Parse it here, once, into reusable helper objects. + // const parsed = raremetal.helpers.parsePortalJSON(agg_source_data); + // const groups = parsed[0]; + // const variants = parsed[1]; + // + // ///////// + // // Post-process this data with any annotations required by data tables on this page + // + // // The aggregation results use the unique ENSEMBL ID for a gene. The gene source tells us how to connect + // // that to a human-friendly gene name (as displayed in the LZ plot) + // const _genes_lookup = {}; + // gene_source_data.forEach(function (gene) { + // const gene_id = gene.gene_id.split('.')[0]; // Ignore ensembl version on gene ids + // _genes_lookup[gene_id] = gene.gene_name; + // }); + // groups.data.forEach(function (one_result) { + // const this_group = groups.getOne(one_result.mask, one_result.group); + // // Add synthetic fields that are not part of the raw calculation results + // one_result.group_display_name = _genes_lookup[one_result.group] || one_result.group; + // one_result.variant_count = this_group.variants.length; + // }); + // + // // When new data has been received (and post-processed), pass it on to any UI elements that use that data + // resultStorage({ + // groups: groups, + // variants: variants, + // }); + // }, + // { discrete: true } + // ); // When results are updated, make sure we are not "drilling down" into a calculation that no longer exists resultStorage.subscribe(aggregationTable.renderData.bind(aggregationTable)); diff --git a/package-lock.json b/package-lock.json index 2880be84..099e6bcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, "requires": { "@babel/highlight": "^7.12.13" } @@ -296,8 +295,7 @@ "@babel/helper-validator-identifier": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" }, "@babel/helper-validator-option": { "version": "7.12.17", @@ -332,7 +330,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.12.11", "chalk": "^2.0.0", @@ -343,7 +340,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -352,7 +348,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -363,7 +358,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -1623,8 +1617,7 @@ "acorn-jsx": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==" }, "acorn-walk": { "version": "7.2.0", @@ -1646,7 +1639,6 @@ "version": "6.12.3", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1666,6 +1658,11 @@ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -1707,7 +1704,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -1955,8 +1951,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -1989,7 +1984,6 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2103,8 +2097,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camelcase": { "version": "5.3.1", @@ -2154,6 +2147,11 @@ "supports-color": "^2.0.0" } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -2201,6 +2199,19 @@ "del": "^4.1.1" } }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -2261,7 +2272,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -2269,8 +2279,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colorette": { "version": "1.2.1", @@ -2301,8 +2310,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "convert-source-map": { "version": "1.7.0", @@ -2659,7 +2667,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -2688,8 +2695,7 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "deepmerge": { "version": "4.2.2", @@ -2756,7 +2762,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "requires": { "esutils": "^2.0.2" } @@ -2851,8 +2856,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.14.3", @@ -3168,14 +3172,12 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, "requires": { "estraverse": "^5.1.0" }, @@ -3183,8 +3185,7 @@ "estraverse": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" } } }, @@ -3192,7 +3193,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "requires": { "estraverse": "^5.2.0" }, @@ -3200,22 +3200,19 @@ "estraverse": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" } } }, "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "events": { "version": "3.2.0", @@ -3246,6 +3243,16 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -3255,20 +3262,17 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fastest-levenshtein": { "version": "1.0.12", @@ -3276,6 +3280,14 @@ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3412,8 +3424,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.1.3", @@ -3431,8 +3442,7 @@ "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, "gensync": { "version": "1.0.0-beta.2", @@ -3488,7 +3498,6 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3516,8 +3525,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { "version": "6.1.0", @@ -3589,8 +3597,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.1", @@ -3665,14 +3672,12 @@ "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3745,8 +3750,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { "version": "4.0.0", @@ -3758,7 +3762,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -3767,8 +3770,68 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } }, "interpret": { "version": "2.2.0", @@ -3809,8 +3872,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-glob": { "version": "4.0.1", @@ -3899,8 +3961,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -4111,14 +4172,12 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -4230,14 +4289,12 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, "json-stringify-safe": { "version": "5.0.1", @@ -4266,6 +4323,10 @@ "verror": "1.10.0" } }, + "jszlib": { + "version": "git+https://github.com/dasmoth/jszlib.git#4e562c7b88749f3e1e402737ddef5f96bf0906c6", + "from": "git+https://github.com/dasmoth/jszlib.git#4e562c7" + }, "just-clone": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-3.2.1.tgz", @@ -4366,8 +4427,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.debounce": { "version": "4.0.8", @@ -4540,7 +4600,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4548,8 +4607,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "mkdirp": { "version": "1.0.4", @@ -4723,8 +4781,12 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" }, "nanoid": { "version": "3.1.20", @@ -4735,8 +4797,7 @@ "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" }, "neo-async": { "version": "2.6.2", @@ -4744,6 +4805,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, "nise": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", @@ -5099,7 +5165,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -5127,6 +5192,11 @@ "word-wrap": "^1.2.3" } }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, "p-limit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", @@ -5181,7 +5251,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "requires": { "callsites": "^3.0.0" } @@ -5201,14 +5270,12 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, "path-key": { "version": "3.1.1", @@ -5315,8 +5382,7 @@ "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" }, "psl": { "version": "1.8.0", @@ -5586,8 +5652,31 @@ "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "^1.0.0" + } + } + } }, "rimraf": { "version": "3.0.2", @@ -5598,11 +5687,24 @@ "glob": "^7.1.3" } }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, "rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -5676,8 +5778,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "serialize-javascript": { "version": "5.0.1", @@ -5721,8 +5822,7 @@ "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "sinon": { "version": "6.3.5", @@ -5902,8 +6002,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", @@ -5938,7 +6037,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -5947,14 +6045,12 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -6000,6 +6096,314 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "tabix-reader": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tabix-reader/-/tabix-reader-1.0.1.tgz", + "integrity": "sha512-AtGAUP9yLamN8X6yJqWHZqDlTb2DcEyoBHlNVRrVjyLyshVUVnVi0Fwvl4Ag8n49yGH7YoRYgMemmy5L0pQ6lg==", + "requires": { + "eslint": "^5.16.0", + "jszlib": "git+https://github.com/dasmoth/jszlib.git#4e562c7" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==" + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==" + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "table": { "version": "6.0.7", "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", @@ -6185,8 +6589,20 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } }, "to-fast-properties": { "version": "2.0.0", @@ -6217,8 +6633,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tunnel-agent": { "version": "0.6.0", @@ -6318,7 +6733,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" }, @@ -6326,8 +6740,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, @@ -6615,8 +7028,7 @@ "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, "workerpool": { "version": "6.1.0", @@ -6696,8 +7108,25 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "requires": { + "mkdirp": "^0.5.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + } + } }, "write-file-atomic": { "version": "3.0.3", diff --git a/package.json b/package.json index ad251783..e4becab8 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "d3": "^5.16.0", "deepmerge": "^4.2.2", "just-clone": "^3.2.1", + "tabix-reader": "^1.0.1", "undercomplicate": "^0.1.0" }, "devDependencies": { diff --git a/test/unit/components/test_plot.js b/test/unit/components/test_plot.js index 30abd54c..9e2f8c64 100644 --- a/test/unit/components/test_plot.js +++ b/test/unit/components/test_plot.js @@ -424,7 +424,7 @@ describe('LocusZoom.Plot', function() { }); }); - describe.skip('subscribeToData', function() { + describe('subscribeToData', function() { beforeEach(function() { const layout = { panels: [{ id: 'panel0' }], From 38db417f740b16048efda79bad1836606d30ee13 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Fri, 17 Sep 2021 23:06:49 -0400 Subject: [PATCH 027/100] Re-implement new subscribeToData method + "just give me the same as layer x" mode. Also update aggtest demo to use the new syntax. --- esm/components/data_layer/base.js | 6 + esm/components/plot.js | 80 +++++++++-- examples/js/aggregation-tests-example-page.js | 83 ++++++------ test/unit/components/test_plot.js | 127 ++++++++++-------- 4 files changed, 190 insertions(+), 106 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index a56bab57..20d813e2 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -1527,6 +1527,12 @@ class BaseDataLayer { this.data = new_data; this.applyDataMethods(); this.initialized = true; + // Allow listeners (like subscribeToData) to see the information associated with a layer + this.parent.emit( + 'data_from_layer', + { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin + true + ); }); } } diff --git a/esm/components/plot.js b/esm/components/plot.js index b6d6bb38..a26689a0 100644 --- a/esm/components/plot.js +++ b/esm/components/plot.js @@ -66,6 +66,16 @@ const default_layout = { * @see event:any_lz_event */ +/** + * One particular data layer has completed a request for data. + * // FIXME: docs should be revised once this new event is stabilized + * @event data_from_layer + * @property {object} data The data used to draw this layer: an array where each element represents one row/ datum + * element. It reflects all namespaces and data operations used by that layer. + * @see event:any_lz_event + */ + + /** * An action occurred that changed, or could change, the layout. * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight, @@ -614,6 +624,8 @@ class Plot { * @callback externalDataCallback * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to * a data layer making an equivalent request. + * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the + * structure of the data, instead of just displaying something. */ /** @@ -631,30 +643,74 @@ class Plot { * * @public * @listens event:data_rendered - * @param {String[]} fields An array of field names and transforms, in the same syntax used by a data layer. - * Different data sources should be prefixed by the namespace name. - * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that - * new data is received by the plot. Receives two arguments: (data, plot). + * @listens event:data_from_layer * @param {Object} [opts] Options + * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched. + * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details. + * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested, + * this is usually required in order to specify dependency order and join operations. See data layer documentation for details. * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem * occurs during the data request or subsequent callback operations - * @param {boolean} [opts.discrete=false] Normally the callback will subscribe to the combined body from the chain, - * which may not be in a format that matches what the external callback wants to do. If discrete=true, returns the - * uncombined record info + * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that + * new data is received by the plot. Receives two arguments: (data, plot). * @return {function} The newly created event listener, to allow for later cleanup/removal */ - subscribeToData(fields, success_callback, opts) { - opts = opts || {}; + subscribeToData(opts, success_callback) { + const { from_layer, namespace, data_operations, onerror } = opts; // Register an event listener that is notified whenever new data has been rendered - const error_callback = opts.onerror || function (err) { + const error_callback = onerror || function (err) { console.error('An error occurred while acting on an external callback', err); }; + if (from_layer) { + // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places. + const base_prefix = `${this.getBaseId()}.`; + // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code. + const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`; + + // Ensure that a valid layer exists to watch + let is_valid_layer = false; + for (let p of Object.values(this.panels)) { + is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target); + if (is_valid_layer) { + break; + } + } + if (!is_valid_layer) { + throw new Error(`Could not subscribe to unknown data layer ${layer_target}`); + } + + const listener = (eventData) => { + if (eventData.data.layer !== layer_target) { + // Same event name fires for many layers; only fire success cb for the one layer we want + return; + } + try { + success_callback(eventData.data.content, this); + } catch (error) { + error_callback(error); + } + }; + + this.on('data_from_layer', listener); + return listener; + } + + // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of + // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized + // in connection to some other visualization) + if (!namespace) { + throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option"); + } + + const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); const listener = () => { try { - this.lzd.getData(this.state, fields) - .then((new_data) => success_callback(opts.discrete ? new_data.discrete : new_data.body, this)) + // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely, + // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame. + this.lzd.getData(this.state, entities, dependencies) + .then((new_data) => success_callback(new_data, this)) .catch(error_callback); } catch (error) { // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up diff --git a/examples/js/aggregation-tests-example-page.js b/examples/js/aggregation-tests-example-page.js index f8b2f6fc..15ba24d9 100644 --- a/examples/js/aggregation-tests-example-page.js +++ b/examples/js/aggregation-tests-example-page.js @@ -326,45 +326,50 @@ function setupWidgetListeners(plot, aggregationTable, variantsTable, resultStora } }.bind(this)); - // plot.subscribeToData( - // ['aggregation:all', 'gene:all'], - // function (data) { - // // chain.discrete provides distinct data from each source - // const gene_source_data = data.gene; - // const agg_source_data = data.aggregation; - // - // const results = agg_source_data.results; - // - // // Aggregation calcs return very complex data. Parse it here, once, into reusable helper objects. - // const parsed = raremetal.helpers.parsePortalJSON(agg_source_data); - // const groups = parsed[0]; - // const variants = parsed[1]; - // - // ///////// - // // Post-process this data with any annotations required by data tables on this page - // - // // The aggregation results use the unique ENSEMBL ID for a gene. The gene source tells us how to connect - // // that to a human-friendly gene name (as displayed in the LZ plot) - // const _genes_lookup = {}; - // gene_source_data.forEach(function (gene) { - // const gene_id = gene.gene_id.split('.')[0]; // Ignore ensembl version on gene ids - // _genes_lookup[gene_id] = gene.gene_name; - // }); - // groups.data.forEach(function (one_result) { - // const this_group = groups.getOne(one_result.mask, one_result.group); - // // Add synthetic fields that are not part of the raw calculation results - // one_result.group_display_name = _genes_lookup[one_result.group] || one_result.group; - // one_result.variant_count = this_group.variants.length; - // }); - // - // // When new data has been received (and post-processed), pass it on to any UI elements that use that data - // resultStorage({ - // groups: groups, - // variants: variants, - // }); - // }, - // { discrete: true } - // ); + LocusZoom.JoinFunctions.add('agg_results_table_format', (agg_data, gene_data) => { + // Aggregation calcs return very complex data. Parse it here, once, into reusable helper objects. + const parsed = raremetal.helpers.parsePortalJSON(agg_data); + const groups = parsed[0]; + const variants = parsed[1]; + + ///////// + // Post-process this data with any annotations required by data tables on this page + + // The aggregation results use the unique ENSEMBL ID for a gene. The gene source tells us how to connect + // that to a human-friendly gene name (as displayed in the LZ plot) + const _genes_lookup = {}; + gene_data.forEach(function (gene) { + const gene_id = gene.gene_id.split('.')[0]; // Ignore ensembl version on gene ids + _genes_lookup[gene_id] = gene.gene_name; + }); + groups.data.forEach(function (one_result) { + const this_group = groups.getOne(one_result.mask, one_result.group); + // Add synthetic fields that are not part of the raw calculation results + one_result.group_display_name = _genes_lookup[one_result.group] || one_result.group; + one_result.variant_count = this_group.variants.length; + }); + return [groups, variants]; + }); + + plot.subscribeToData( + { + namespace: {'aggregation': 'aggregation', 'gene': 'gene' }, + data_operations: [{ + type: 'agg_results_table_format', + requires: ['aggregation', 'gene'], + }], + }, + (result) => { + // Data ops are designed to return one consolidated stream, so the data op above returns... a tuple + // This is old code and we're not investing heavily in bringing it up to modern spec + const [groups, variants] = result; + // When new data has been received (and post-processed), pass it on to any UI elements that use that data + resultStorage({ + groups: groups, + variants: variants, + }); + } + ); // When results are updated, make sure we are not "drilling down" into a calculation that no longer exists resultStorage.subscribe(aggregationTable.renderData.bind(aggregationTable)); diff --git a/test/unit/components/test_plot.js b/test/unit/components/test_plot.js index 9e2f8c64..6813628e 100644 --- a/test/unit/components/test_plot.js +++ b/test/unit/components/test_plot.js @@ -427,12 +427,21 @@ describe('LocusZoom.Plot', function() { describe('subscribeToData', function() { beforeEach(function() { const layout = { - panels: [{ id: 'panel0' }], + panels: [ + { id: 'panel0' }, + { + id: 'panel1', + data_layers: [ { id: 'layer1', type: 'line', namespace: { 'first': 'first' } } ], + }, + ], }; this.first_source_data = [ { x: 0, y: false }, { x: 1, y: true } ]; this.data_sources = new DataSources() - .add('first', ['StaticJSON', {data: this.first_source_data }]); + .add('first', ['StaticSource', { + prefix_namespace: false, // Makes it easier to write tests- "data in === data out" + data: this.first_source_data, + }]); d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', this.data_sources, layout); @@ -443,67 +452,76 @@ describe('LocusZoom.Plot', function() { sinon.restore(); }); - it('allows subscribing to data using a standard limited fields array', function (done) { - const expectedData = [{ 'first:x': 0 }, { 'first:x': 1 }]; + it('requires specifying one of two ways to subscribe', function () { + assert.throws( + () => this.plot.subscribeToData({ useless_option: 'something' }), + /must specify the desired data/ + ); + }); - const dataCallback = sinon.spy(); + it('allows subscribing to the data of a layer by name', function () { + const callbackShort = sinon.spy(); + const callbackLong = sinon.spy(); - this.plot.subscribeToData( - [ 'first:x' ], - dataCallback, - { onerror: done } - ); + const listener = this.plot.subscribeToData({ from_layer: 'panel1.layer1' }, callbackShort); + assert.ok(listener, 'Subscribes to partial path (panel.layer syntax)'); - this.plot.applyState().catch(done); + const listener2 = this.plot.subscribeToData({ from_layer: 'plot.panel1.layer1' }, callbackLong); + assert.ok(listener2, 'Subscribes to full path (plot.panel.layer syntax)'); - // Ugly hack: callback was not recognized at time of promise resolution, and data_rendered may be fired - // more than once during rerender - window.setTimeout(function() { - try { - assert.ok(dataCallback.called, 'Data handler was called'); - assert.ok(dataCallback.calledWith(expectedData), 'Data handler received the expected data'); - done(); - } catch (error) { - done(error); - } - }, 0); + return this.plot.applyState().then(() => { + assert.ok(callbackShort.called, 'panel.layer callback invoked'); + assert.ok(callbackShort.calledWith(this.first_source_data), 'panel.layer callback receives expected data'); + assert.ok(callbackLong.called, 'plot.panel.layer callback invoked'); + assert.ok(callbackLong.calledWith(this.first_source_data), 'plot.panel.layer callback receives expected data'); + }); }); - it('allows subscribing to individual (not combined) sources', function (done) { - const expectedData = { first: [{ 'first:x': 0 }, { 'first:x': 1 }] }; + it('warns when subscribing to the data of a layer that does not exist', function () { + assert.throws( + () => this.plot.subscribeToData({ from_layer: 'panel0.layer1' }, () => null), + /unknown data layer/, + 'Warns if requesting an unknown layer' + ); + }); + + it('allows subscribing to data using namespace syntax', function () { const dataCallback = sinon.spy(); - this.plot.subscribeToData( - [ 'first:x' ], - dataCallback, - { onerror: done, discrete: true } + const listener = this.plot.subscribeToData( + { namespace: { 'first': 'first' } }, + dataCallback ); - // Ugly hack: callback was not recognized at time of promise resolution, and data_rendered may be fired - // more than once during rerender - window.setTimeout(function() { - try { - assert.ok(dataCallback.called, 'Data handler was called'); - assert.ok(dataCallback.calledWith(expectedData), 'Data handler received the expected data'); - done(); - } catch (error) { - done(error); - } - }, 0); + assert.ok(listener, 'Listener defined'); + + // When data is manually specified, it calls an async method inside an event hook- no problem in a real + // page, but it makes writing async tests a bit tricksy. + // Force the callback to fire and (pretty much mostly) complete + this.plot.emit('data_rendered'); + + // Technically there's a slight chance of race condition still. Watch for random failures. + return this.plot.applyState().then(() => { + assert.ok(dataCallback.called, 'Data handler was called'); + assert.ok(dataCallback.calledWith(this.first_source_data), 'Data handler received the expected data'); + }); }); it('calls an error callback if a problem occurs while fetching data', function() { - const dataCallback = sinon.spy(); + const dataCallback = () => { + throw new Error('Data callback should not be called'); + }; const errorCallback = sinon.spy(); this.plot.subscribeToData( - [ 'nosource:x' ], - dataCallback, - { onerror: errorCallback } + { + from_layer: 'plot.panel1.layer1', + onerror: errorCallback, + }, + dataCallback ); return this.plot.applyState() .then(function() { - assert.ok(dataCallback.notCalled, 'Data callback was not called'); assert.ok(errorCallback.called, 'Error callback was called'); }); }); @@ -513,16 +531,18 @@ describe('LocusZoom.Plot', function() { const errorCallback = sinon.spy(); const listener = this.plot.subscribeToData( - ['nosource:x'], - dataCallback, - { onerror: errorCallback } + { + from_layer: 'plot.panel1.layer1', + onerror: errorCallback, + }, + dataCallback ); - this.plot.off('data_rendered', listener); - this.plot.emit('data_rendered'); - assert.equal( - this.plot.event_hooks['data_rendered'].indexOf(listener), - -1, + this.plot.off('data_from_layer', listener); + this.plot.emit('data_from_layer'); + assert.notInclude( + this.plot.event_hooks['data_from_layer'], + listener, 'Listener should not be registered' ); assert.ok(dataCallback.notCalled, 'listener success callback was not fired'); @@ -598,7 +618,6 @@ describe('LocusZoom.Plot', function() { id_field: 's:id', type: 'scatter', namespace: { s: 's' }, - fields: ['s:id', 's:x'], match: { send: 's:x', receive: 's:x' }, }, { @@ -606,14 +625,12 @@ describe('LocusZoom.Plot', function() { id_field: 's:id', type: 'scatter', namespace: { s: 's' }, - fields: ['s:id', 's:x', 's:y'], }, { id: 'd3', id_field: 's:id', type: 'scatter', namespace: { s: 's' }, - fields: ['s:id', 's:y'], match: { receive: 's:y' }, }, ], From 7edf5d8c02ace0da05b20af9ad311637c502f942 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 20 Sep 2021 13:54:25 -0400 Subject: [PATCH 028/100] Rename JoinFunctions -> DataFunctions, to reflect that other annotation features are possible. Relax the restriction that all data ops must receive exactly two arguments: data funcs now receive all recordsets as a single array-of-recordsets arg --- esm/components/data_layer/base.js | 6 +- esm/data/requester.js | 19 +-- esm/ext/lz-aggregation-tests.js | 2 +- esm/ext/lz-credible-sets.js | 37 ++--- esm/index.js | 4 +- esm/registry/{joins.js => data_ops.js} | 49 +++++-- esm/registry/index.js | 4 +- examples/js/aggregation-tests-example-page.js | 2 +- examples/multiple_phenotypes_layered.html | 16 ++- test/unit/components/test_datalayer.js | 2 +- test/unit/data/test_data_ops.js | 127 ++++++++++++++++++ test/unit/data/test_joins.js | 102 -------------- test/unit/data/test_requester.js | 6 +- 13 files changed, 216 insertions(+), 160 deletions(-) rename esm/registry/{joins.js => data_ops.js} (60%) create mode 100644 test/unit/data/test_data_ops.js delete mode 100644 test/unit/data/test_joins.js diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 20d813e2..9f02d761 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -501,8 +501,10 @@ class BaseDataLayer { // Current implementation is a soft warning, so that certain "incremental enhancement" features // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info. // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data. - console.warn(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}`); - console.warn('Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations"'); + console.warn(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]} + Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" + If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules. +`); } } diff --git a/esm/data/requester.js b/esm/data/requester.js index b4f49968..d204b794 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -1,18 +1,19 @@ import {getLinkedData} from 'undercomplicate'; -import { JOINS } from '../registry'; +import { DATA_OPS } from '../registry'; -class JoinTask { +class DataOperation { constructor(join_type, params) { - this._callable = JOINS.get(join_type); + this._callable = DATA_OPS.get(join_type); this._params = params || []; } - getData(options, left, right) { - // Future: this could support joining > 2 things in one function. - // Joining three things at once is a pretty niche usage, and we won't add this complexity unless clearly needed - return Promise.resolve(this._callable(left, right, ...this._params)); + getData(options, ...requires) { + // Most operations are joins: they receive two pieces of data (eg left + right) + // Other ops are possible, like consolidating just one set of records to best value per key + // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...] + return Promise.resolve(this._callable(requires, ...this._params)); } } @@ -107,7 +108,7 @@ class Requester { } }); - const task = new JoinTask(type, params); + const task = new DataOperation(type, params); entities.set(name, task); dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB) } @@ -136,4 +137,4 @@ class Requester { export default Requester; -export {JoinTask as _JoinTask}; +export {DataOperation as _JoinTask}; diff --git a/esm/ext/lz-aggregation-tests.js b/esm/ext/lz-aggregation-tests.js index 2b53238c..e61344e8 100644 --- a/esm/ext/lz-aggregation-tests.js +++ b/esm/ext/lz-aggregation-tests.js @@ -219,7 +219,7 @@ function install (LocusZoom) { } } - LocusZoom.JoinFunctions.add('gene_plus_aggregation', (genes_data, aggregation_data) => { + LocusZoom.DataFunctions.add('gene_plus_aggregation', (genes_data, aggregation_data) => { // Used to highlight genes with significant aggtest results. Unlike a basic left join, it chooses one specific aggtest with the most significant results // Tie the calculated group-test results to genes with a matching name diff --git a/esm/ext/lz-credible-sets.js b/esm/ext/lz-credible-sets.js index 2840006f..da846ed5 100644 --- a/esm/ext/lz-credible-sets.js +++ b/esm/ext/lz-credible-sets.js @@ -45,7 +45,6 @@ function install (LocusZoom) { */ class CredibleSetLZ extends BaseUMAdapter { /** - * @param {String} config.params.join_fields.log_pvalue The name of the field containing -log10 pvalue information * @param {Number} [config.params.threshold=0.95] The credible set threshold (eg 95%). Will continue selecting SNPs * until the posterior probabilities add up to at least this fraction of the total. * @param {Number} [config.params.significance_threshold=7.301] Do not perform a credible set calculation for this @@ -55,10 +54,6 @@ function install (LocusZoom) { */ constructor(config) { super(...arguments); - if (!(this._config.join_fields && this._config.join_fields.log_pvalue)) { - throw new Error(`Source config for ${this.constructor.name} must specify how to find 'fields:log_pvalue'`); - } - // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p) this._config = Object.assign( { threshold: 0.95, significance_threshold: 7.301 }, @@ -95,7 +90,7 @@ function install (LocusZoom) { if (!nlogpvals.some((val) => val >= this._config.significance_threshold)) { // If NO points have evidence of significance, define the credible set to be empty // (rather than make a credible set that we don't think is meaningful) - return Promise.resolve([]); + return Promise.resolve(_assoc_data); } try { @@ -165,19 +160,22 @@ function install (LocusZoom) { const association_credible_set_layer = function () { const base = LocusZoom.Layouts.get('data_layer', 'association_pvalues', { - unnamespaced: true, id: 'associationcredibleset', - namespace: { 'assoc': 'assoc', 'credset(assoc)': 'credset', 'ld(assoc)': 'ld' }, + namespace: { 'assoc': 'assoc', 'credset': 'credset', 'ld': 'ld' }, + data_operations: [ + { + type: 'fetch', + from: ['assoc', 'ld(assoc)', 'credset(assoc)'], + }, + { + type: 'left_match', + name: 'combined', + requires: ['credset', 'ld'], // The credible sets demo wasn't fully moved over to the new data operations system, and as such it is a bit weird + params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later. + }, + ], fill_opacity: 0.7, tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set', { unnamespaced: true }), - fields: [ - 'assoc:variant', 'assoc:position', - 'assoc:log_pvalue', 'assoc:log_pvalue|logtoscinotation', - 'assoc:ref_allele', - 'credset:posterior_prob', 'credset:contrib_fraction', - 'credset:is_member', - 'ld:state', 'ld:isrefvar', - ], match: { send: 'assoc:variant', receive: 'assoc:variant' }, }); base.color.unshift({ @@ -199,7 +197,11 @@ function install (LocusZoom) { * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions */ const annotation_credible_set_layer = { - namespace: { 'assoc': 'assoc', 'credset(assoc)': 'credset' }, + namespace: { 'assoc': 'assoc', 'credset': 'credset' }, + data_operations: [{ + type: 'fetch', + from: ['assoc', 'credset(assoc)'], + }], id: 'annotationcredibleset', type: 'annotation_track', id_field: 'assoc:variant', @@ -217,7 +219,6 @@ function install (LocusZoom) { }, '#00CC00', ], - fields: ['assoc:variant', 'assoc:position', 'assoc:log_pvalue', 'credset:posterior_prob', 'credset:contrib_fraction', 'credset:is_member'], match: { send: 'assoc:variant', receive: 'assoc:variant' }, filters: [ // Specify which points to show on the track. Any selection must satisfy ALL filters diff --git a/esm/index.js b/esm/index.js index 1b02086e..dcea1ae0 100644 --- a/esm/index.js +++ b/esm/index.js @@ -14,7 +14,7 @@ import { populate } from './helpers/display'; import { ADAPTERS as Adapters, DATA_LAYERS as DataLayers, - JOINS as JoinFunctions, + DATA_OPS as DataFunctions, LAYOUTS as Layouts, MATCHERS as MatchFunctions, SCALABLE as ScaleFunctions, @@ -31,7 +31,7 @@ const LocusZoom = { // Registries for plugin system Adapters, DataLayers, - JoinFunctions, + DataFunctions, Layouts, MatchFunctions, ScaleFunctions, diff --git a/esm/registry/joins.js b/esm/registry/data_ops.js similarity index 60% rename from esm/registry/joins.js rename to esm/registry/data_ops.js index d9ca23cd..503073d0 100644 --- a/esm/registry/joins.js +++ b/esm/registry/data_ops.js @@ -1,9 +1,17 @@ /** - * "Join" functions + * "Data operation" functions, with call signature ( [recordsetA, recordsetB...], ...params) => combined_results * - * Connect two sets of records together according to predefined rules. + * These usually operate on two recordsets (joins), but can operate on one recordset (eg grouping) or > 2 (hopefully rare) * - * @module LocusZoom_JoinFunctions + * Pieces of code designed to transform or connect well structured data, usually as returned from an API + * + * Most of these are `joins`: intended to combine two recordsets (like assoc + ld) to a single final source of truth + * + * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some, + * particularly for advanced features, may carry assumptions about field names/ formatting. + * (example: log_pvalue instead of pvalue, or variant specifier formats) + * + * @module LocusZoom_DataFunctions */ import {joins} from 'undercomplicate'; @@ -11,21 +19,26 @@ import {RegistryBase} from './base'; /** * A plugin registry that allows plots to use both pre-defined and user-provided "data join" functions. - * @alias module:LocusZoom~JoinFunctions + * @alias module:LocusZoom~DataFunctions * @type {module:registry/base~RegistryBase} */ const registry = new RegistryBase(); -registry.add('left_match', joins.left_match); - -registry.add('inner_match', joins.inner_match); - -registry.add('full_outer_match', joins.full_outer_match); +function _wrap_join(handle) { + // Validate number of arguments and convert call signature from (deps, ...params) to (left, right, ...params). + // Must data operations are joins, so this wrapper is common shared code. + return (deps, ...params) => { + if (deps.length !== 2) { + throw new Error('Join functions must receive exactly two recordsets'); + } + return handle(...deps, ...params); + }; +} // Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to // pick the most significant claim in the catalog for a variant, rather than joining every possible match. // This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts. -registry.add('assoc_to_gwas_catalog', (assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) => { +function assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) { if (!assoc_data.length) { return assoc_data; } @@ -49,10 +62,10 @@ registry.add('assoc_to_gwas_catalog', (assoc_data, catalog_data, assoc_key, cata catalog_flat.push(best_variant); } return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key); -}); +} // Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them. -registry.add('genes_to_gnomad_constraint', (genes_data, constraint_data) => { +function genes_to_gnomad_constraint(genes_data, constraint_data) { genes_data.forEach(function(gene) { // Find payload keys that match gene names in this response const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names @@ -71,6 +84,16 @@ registry.add('genes_to_gnomad_constraint', (genes_data, constraint_data) => { } }); return genes_data; -}); +} + +registry.add('left_match', _wrap_join(joins.left_match)); + +registry.add('inner_match', _wrap_join(joins.inner_match)); + +registry.add('full_outer_match', _wrap_join(joins.full_outer_match)); + +registry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog)); + +registry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint)); export default registry; diff --git a/esm/registry/index.js b/esm/registry/index.js index 11897896..d0a20b22 100644 --- a/esm/registry/index.js +++ b/esm/registry/index.js @@ -4,7 +4,7 @@ import ADAPTERS from './adapters'; import DATA_LAYERS from './data_layers'; import LAYOUTS from './layouts'; -import JOINS from './joins'; +import DATA_OPS from './data_ops'; import MATCHERS from './matchers'; import SCALABLE from './scalable'; import TRANSFORMS from './transforms'; @@ -14,5 +14,5 @@ export { // Base classes and reusable components ADAPTERS, DATA_LAYERS, LAYOUTS, WIDGETS, // User defined functions for injecting custom behavior into layout directives - JOINS, MATCHERS, SCALABLE, TRANSFORMS, + DATA_OPS, MATCHERS, SCALABLE, TRANSFORMS, }; diff --git a/examples/js/aggregation-tests-example-page.js b/examples/js/aggregation-tests-example-page.js index 15ba24d9..45528031 100644 --- a/examples/js/aggregation-tests-example-page.js +++ b/examples/js/aggregation-tests-example-page.js @@ -326,7 +326,7 @@ function setupWidgetListeners(plot, aggregationTable, variantsTable, resultStora } }.bind(this)); - LocusZoom.JoinFunctions.add('agg_results_table_format', (agg_data, gene_data) => { + LocusZoom.DataFunctions.add('agg_results_table_format', (agg_data, gene_data) => { // Aggregation calcs return very complex data. Parse it here, once, into reusable helper objects. const parsed = raremetal.helpers.parsePortalJSON(agg_data); const groups = parsed[0]; diff --git a/examples/multiple_phenotypes_layered.html b/examples/multiple_phenotypes_layered.html index be693eaa..27df8676 100644 --- a/examples/multiple_phenotypes_layered.html +++ b/examples/multiple_phenotypes_layered.html @@ -106,8 +106,6 @@

Top Hits

phenos.forEach(function(pheno){ data_sources.add(pheno.namespace, ["AssociationLZ", {url: apiBase + "statistic/single/", source: pheno.study_id, id_field: "variant" }]); var association_data_layer_mods = { - // FIXME: Merge logic prevents us from removing a namespace- LD is still present after override! - namespace: { "assoc": pheno.namespace }, join_options: [], id: "associationpvalues_" + pheno.namespace, name: pheno.title, @@ -122,12 +120,18 @@

Top Hits

show: { or: ["highlighted", "selected"] }, hide: { and: ["unhighlighted", "unselected"] }, html: "" + pheno.title + "
" - + "{{" + pheno.namespace + ":variant|htmlescape}}
" - + "P Value: {{" + pheno.namespace + ":log_pvalue|logtoscinotation|htmlescape}}
" - + "Ref. Allele: {{" + pheno.namespace + ":ref_allele|htmlescape}}
" + + "{{assoc:variant|htmlescape}}
" + + "P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
" + + "Ref. Allele: {{assoc:ref_allele|htmlescape}}
" } }; - layout.panels[0].data_layers.push(LocusZoom.Layouts.get("data_layer", "association_pvalues", association_data_layer_mods)); + // Much like subclassing, overrides syntax isn't wel suited to removing things. Since we don't want to show + // LD in this vis, we need to replace "namespace" and "data_operations" manually (if we put namespace in the overrides above, + // it would merge the new namespaces with the old one, and LD would still be there) + const layer_layout = LocusZoom.Layouts.get("data_layer", "association_pvalues", association_data_layer_mods); + layer_layout.namespace = { "assoc": pheno.namespace }; + layer_layout.data_operations = []; + layout.panels[0].data_layers.push(layer_layout); }); // Generate the LocusZoom plot diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index 54ad55aa..cba10703 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -27,7 +27,7 @@ describe('LocusZoom.DataLayer', function () { let spy = sinon.spy(console, 'warn'); this.layer.data = [{ 'assoc:variant': '1:23_A/B', 'assoc:position': 23 }]; this.layer.applyDataMethods(); - assert.ok(spy.calledTwice, 'Console.warn was called with data contract errors'); + assert.ok(spy.calledOnce, 'Console.warn was called with data contract errors'); assert.ok(spy.firstCall.args[0].match(/Missing fields are: assoc:rsid/), 'Developer message identifies the missing fields'); }); diff --git a/test/unit/data/test_data_ops.js b/test/unit/data/test_data_ops.js new file mode 100644 index 00000000..e31d58b2 --- /dev/null +++ b/test/unit/data/test_data_ops.js @@ -0,0 +1,127 @@ +/** + * Test custom join logic specific to LZ core code + */ + +import {assert} from 'chai'; + +import {DATA_OPS} from '../../../esm/registry'; + +describe('Data operations', function() { + describe('Common behavior', function () { + it('validates two-argument (join) functions', function () { + const func = DATA_OPS.get('left_match'); + assert.throws( + () => func([1, 2, 3], 'paramA'), + /must receive exactly two recordsets/ + ); + }); + }); + + describe('Custom join operations', function () { + describe('assoc_to_gwas_catalog', function () { + before(function() { + this.func = DATA_OPS.get('assoc_to_gwas_catalog'); + }); + + beforeEach(function () { + this.assoc_data = [ + { 'assoc:chromosome': 1, 'assoc:position': 2 }, + { 'assoc:chromosome': 1, 'assoc:position': 4 }, + { 'assoc:chromosome': 1, 'assoc:position': 6 }, + ]; + this.catalog_data = [ + { 'catalog:chrom': 1, 'catalog:pos': 3, 'catalog:log_pvalue': 1.3, 'catalog:rsid': 'rs3', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 4, 'catalog:log_pvalue': 1.4, 'catalog:rsid': 'rs4', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 5, 'catalog:log_pvalue': 1.5, 'catalog:rsid': 'rs5', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 6, 'catalog:log_pvalue': 1.6, 'catalog:rsid': 'rs6', 'catalog:trait': 'arithomania' }, + ]; + }); + + it('aligns records based on loose position match', function () { + const { func, assoc_data, catalog_data } = this; + const res = func( + [assoc_data, catalog_data], + 'assoc:position', + 'catalog:pos', + 'catalog:log_pvalue' + ); + + assert.deepEqual(res, [ + { 'assoc:chromosome': 1, 'assoc:position': 2 }, // No annotations available for this point + { + 'assoc:chromosome': 1, + 'assoc:position': 4, + 'catalog:rsid': 'rs4', + 'catalog:trait': 'arithomania', + 'catalog:chrom': 1, + 'catalog:log_pvalue': 1.4, + 'catalog:pos': 4, + 'n_catalog_matches': 1, + }, + { + 'assoc:chromosome': 1, + 'assoc:position': 6, + 'catalog:rsid': 'rs6', + 'catalog:trait': 'arithomania', + 'catalog:chrom': 1, + 'catalog:log_pvalue': 1.6, + 'catalog:pos': 6, + 'n_catalog_matches': 1, + }, + ]); + }); + + it('handles the case where the same SNP has more than one catalog entry', function () { + const { assoc_data, func } = this; + const catalog_data = [ + { 'catalog:chrom': 1, 'catalog:pos': 4, 'catalog:log_pvalue': 1.40, 'catalog:rsid': 'rs4', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 4, 'catalog:log_pvalue': 1.41, 'catalog:rsid': 'rs4', 'catalog:trait': 'graphomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 6, 'catalog:log_pvalue': 1.61, 'catalog:rsid': 'rs6', 'catalog:trait': 'arithomania' }, + { 'catalog:chrom': 1, 'catalog:pos': 6, 'catalog:log_pvalue': 1.60, 'catalog:rsid': 'rs6', 'catalog:trait': 'graphomania' }, + ]; + + const res = func( + [assoc_data, catalog_data], + 'assoc:position', + 'catalog:pos', + 'catalog:log_pvalue' + ); + + assert.deepEqual(res, [ + { 'assoc:chromosome': 1, 'assoc:position': 2 }, // No annotations available for this point + { + 'assoc:chromosome': 1, + 'assoc:position': 4, + 'catalog:chrom': 1, + 'catalog:log_pvalue': 1.41, + 'catalog:pos': 4, + 'catalog:rsid': 'rs4', + 'catalog:trait': 'graphomania', + 'n_catalog_matches': 2, + }, + { + 'assoc:chromosome': 1, + 'assoc:position': 6, + 'catalog:chrom': 1, + 'catalog:log_pvalue': 1.61, + 'catalog:pos': 6, + 'catalog:rsid': 'rs6', + 'catalog:trait': 'arithomania', + 'n_catalog_matches': 2, + }, + ]); + }); + + it('gracefully handles no catalog entries in region', function () { + const { func, assoc_data } = this; + const res = func( + [assoc_data, []], + 'assoc:position', + 'catalog:pos', + 'catalog:log_pvalue' + ); + assert.deepEqual(res, assoc_data); + }); + }); + }); +}); diff --git a/test/unit/data/test_joins.js b/test/unit/data/test_joins.js deleted file mode 100644 index b3d0c6d8..00000000 --- a/test/unit/data/test_joins.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Test custom join logic specific to LZ core code - */ - -import {assert} from 'chai'; - -import {JOINS} from '../../../esm/registry'; - - -describe('Custom join operations', function () { - describe('assoc_to_gwas_catalog', function () { - before(function() { - this.func = JOINS.get('assoc_to_gwas_catalog'); - }); - - beforeEach(function () { - this.assoc_data = [ - { 'assoc:chromosome': 1, 'assoc:position': 2 }, - { 'assoc:chromosome': 1, 'assoc:position': 4 }, - { 'assoc:chromosome': 1, 'assoc:position': 6 }, - ]; - this.catalog_data = [ - { 'catalog:chrom': 1, 'catalog:pos': 3, 'catalog:log_pvalue': 1.3, 'catalog:rsid': 'rs3', 'catalog:trait': 'arithomania' }, - { 'catalog:chrom': 1, 'catalog:pos': 4, 'catalog:log_pvalue': 1.4, 'catalog:rsid': 'rs4', 'catalog:trait': 'arithomania' }, - { 'catalog:chrom': 1, 'catalog:pos': 5, 'catalog:log_pvalue': 1.5, 'catalog:rsid': 'rs5', 'catalog:trait': 'arithomania' }, - { 'catalog:chrom': 1, 'catalog:pos': 6, 'catalog:log_pvalue': 1.6, 'catalog:rsid': 'rs6', 'catalog:trait': 'arithomania' }, - ]; - }); - - it('aligns records based on loose position match', function () { - const { func, assoc_data, catalog_data } = this; - const res = func(assoc_data, catalog_data, 'assoc:position', 'catalog:pos', 'catalog:log_pvalue'); - - assert.deepEqual(res, [ - { 'assoc:chromosome': 1, 'assoc:position': 2 }, // No annotations available for this point - { - 'assoc:chromosome': 1, - 'assoc:position': 4, - 'catalog:rsid': 'rs4', - 'catalog:trait': 'arithomania', - 'catalog:chrom': 1, - 'catalog:log_pvalue': 1.4, - 'catalog:pos': 4, - 'n_catalog_matches': 1, - }, - { - 'assoc:chromosome': 1, - 'assoc:position': 6, - 'catalog:rsid': 'rs6', - 'catalog:trait': 'arithomania', - 'catalog:chrom': 1, - 'catalog:log_pvalue': 1.6, - 'catalog:pos': 6, - 'n_catalog_matches': 1, - }, - ]); - }); - - it('handles the case where the same SNP has more than one catalog entry', function () { - const { assoc_data, func } = this; - const catalog_data = [ - { 'catalog:chrom': 1, 'catalog:pos': 4, 'catalog:log_pvalue': 1.40, 'catalog:rsid': 'rs4', 'catalog:trait': 'arithomania' }, - { 'catalog:chrom': 1, 'catalog:pos': 4, 'catalog:log_pvalue': 1.41, 'catalog:rsid': 'rs4', 'catalog:trait': 'graphomania' }, - { 'catalog:chrom': 1, 'catalog:pos': 6, 'catalog:log_pvalue': 1.61, 'catalog:rsid': 'rs6', 'catalog:trait': 'arithomania' }, - { 'catalog:chrom': 1, 'catalog:pos': 6, 'catalog:log_pvalue': 1.60, 'catalog:rsid': 'rs6', 'catalog:trait': 'graphomania' }, - ]; - - - const res = func(assoc_data, catalog_data, 'assoc:position', 'catalog:pos', 'catalog:log_pvalue'); - - assert.deepEqual(res, [ - { 'assoc:chromosome': 1, 'assoc:position': 2 }, // No annotations available for this point - { - 'assoc:chromosome': 1, - 'assoc:position': 4, - 'catalog:chrom': 1, - 'catalog:log_pvalue': 1.41, - 'catalog:pos': 4, - 'catalog:rsid': 'rs4', - 'catalog:trait': 'graphomania', - 'n_catalog_matches': 2, - }, - { - 'assoc:chromosome': 1, - 'assoc:position': 6, - 'catalog:chrom': 1, - 'catalog:log_pvalue': 1.61, - 'catalog:pos': 6, - 'catalog:rsid': 'rs6', - 'catalog:trait': 'arithomania', - 'n_catalog_matches': 2, - }, - ]); - }); - - it('gracefully handles no catalog entries in region', function () { - const { func, assoc_data } = this; - const res = func(assoc_data, [], 'assoc:position', 'catalog:pos', 'catalog:log_pvalue'); - assert.deepEqual(res, assoc_data); - }); - }); -}); diff --git a/test/unit/data/test_requester.js b/test/unit/data/test_requester.js index d267a846..62ef6572 100644 --- a/test/unit/data/test_requester.js +++ b/test/unit/data/test_requester.js @@ -1,17 +1,17 @@ import {assert} from 'chai'; import Requester, {_JoinTask} from '../../../esm/data/requester'; -import {JOINS} from '../../../esm/registry'; +import {DATA_OPS} from '../../../esm/registry'; describe('Requester object defines and parses requests', function () { describe('Layout parsing', function () { before(function () { - JOINS.add('sumtwo', (left, right, some_param) => left + right + some_param); + DATA_OPS.add('sumtwo', ([left, right], some_param) => left + right + some_param); }); after(function () { - JOINS.remove('sumtwo'); + DATA_OPS.remove('sumtwo'); }); beforeEach(function () { From 1bf69295328cd44b84e5b626366f1aed010e0320 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 20 Sep 2021 18:22:52 -0400 Subject: [PATCH 029/100] Allow element IDs to be computed from > 1 key, and apply to PheWAS and coaccessibility layers Reduce the magic in auto-generated fields (phewas:x becomes lz_auto_x, etc)- auto fields shouldn't look like they are coming from an API Fix issue with gene constraint adapter during short-circuit paths, such as interrupted requests --- esm/components/data_layer/base.js | 25 ++++++++++++---- esm/data/adapters.js | 4 +++ esm/ext/lz-forest-track.js | 6 ++-- esm/layouts/index.js | 8 +++-- examples/phewas_forest.html | 2 +- examples/phewas_scatter.html | 1 - test/unit/components/test_datalayer.js | 41 ++++++++++++++++++++++++++ 7 files changed, 74 insertions(+), 13 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 9f02d761..6a49fae9 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -116,7 +116,10 @@ class BaseDataLayer { * this array will be fetched. This represents the "contract" between what data is returned and what data is rendered. * This fields array works in concert with the data retrieval method BaseAdapter.extractFields. * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse - * events, etc. This should be unique to the specified datum. + * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is + * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely + * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is + * your job to assure that all of the expected fields are present in every element) * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact * details vary from one layer to the next. See the Interactivity Tutorial for details. @@ -413,8 +416,12 @@ class BaseDataLayer { /** * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors. + * + * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that + * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data), + * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}` * @protected - * @param {Object} element + * @param {Object} element The data associated with a particular element * @returns {String} */ getElementId (element) { @@ -424,11 +431,19 @@ class BaseDataLayer { return element[id_key]; } - const id_field = this.layout.id_field || 'id'; - if (typeof element[id_field] == 'undefined') { + // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression + const id_field = this.layout.id_field; + let value = element[id_field]; + if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) { + // No field value was found directly, but if it looks like a template expression, next, try parsing that + // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot! + value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient + } + if (value === null || value === undefined) { + // Neither exact field nor template options produced an ID throw new Error('Unable to generate element ID'); } - const element_id = element[id_field].toString().replace(/\W/g, ''); + const element_id = value.toString().replace(/\W/g, ''); // Cache ID value for future calls const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\],])/g, '_'); diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 7f500e8e..8d71a20b 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -376,6 +376,10 @@ class GeneConstraintLZ extends BaseLZAdapter { * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps. */ _normalizeResponse(response_text) { + if (typeof response_text !== 'string') { + // If the query short-circuits, we receive an empty list instead of a string + return response_text; + } const data = JSON.parse(response_text); return data.data; } diff --git a/esm/ext/lz-forest-track.js b/esm/ext/lz-forest-track.js index d534b7ae..fc339d51 100644 --- a/esm/ext/lz-forest-track.js +++ b/esm/ext/lz-forest-track.js @@ -99,9 +99,9 @@ function install (LocusZoom) { const y_scale = `y${this.layout.y_axis.axis}_scale`; // Generate confidence interval paths if fields are defined - if (this.layout.confidence_intervals - && this.layout.fields.includes(this.layout.confidence_intervals.start_field) - && this.layout.fields.includes(this.layout.confidence_intervals.end_field)) { + if (this.layout.confidence_intervals && + this.layout.confidence_intervals.start_field && + this.layout.confidence_intervals.end_field) { // Generate a selection for all forest plot confidence intervals const ci_selection = this.svg.group .selectAll('rect.lz-data_layer-forest.lz-data_layer-forest-ci') diff --git a/esm/layouts/index.js b/esm/layouts/index.js index e68be594..b9e82d5b 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -242,7 +242,8 @@ const coaccessibility_layer = { type: 'arcs', tag: 'coaccessibility', match: { send: 'access:target', receive: 'access:target' }, - id_field: 'access:id', + // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID. + id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}', filters: [ { field: 'access:score', operator: '!=', value: null }, ], @@ -316,6 +317,7 @@ const association_pvalues_catalog_layer = function () { return base; }(); + /** * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API * @name phewas_pvalues @@ -329,9 +331,9 @@ const phewas_pvalues_layer = { point_shape: 'circle', point_size: 70, tooltip_positioning: 'vertical', - id_field: 'phewas:id', + id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}', x_axis: { - field: 'phewas:x', // Synthetic/derived field added by `category_scatter` layer + field: 'lz_auto_x', // Automatically added by the category_scatter layer category_field: 'phewas:trait_group', lower_buffer: 0.025, upper_buffer: 0.025, diff --git a/examples/phewas_forest.html b/examples/phewas_forest.html index 39233b0c..2a90e8c2 100644 --- a/examples/phewas_forest.html +++ b/examples/phewas_forest.html @@ -137,7 +137,7 @@
< return home
y_axis: { axis: 2, category_field: 'phewas:phenotype', // Labels - field: 'phewas:y_offset', // Positions (dynamically added) + field: 'lz_auto_y_offset', // Positions (dynamically added by category_forest layer) }, confidence_intervals: { start_field: 'phewas:ci_start', diff --git a/examples/phewas_scatter.html b/examples/phewas_scatter.html index 73c7efb6..3a4aa43c 100644 --- a/examples/phewas_scatter.html +++ b/examples/phewas_scatter.html @@ -236,7 +236,6 @@

API (for developers)

// Modify the tooltips for PheWAS result data layer points to contain more data. The fields in this sample // tooltip are specific to the LZ-Portal API, and are not guaranteed to be in other PheWAS datasources. - LocusZoom.Layouts.mutate_attrs(layout, '$..data_layers[?(@.tag === "phewas")].fields', (fields) => fields.concat(["phewas:pmid", "phewas:description", "phewas:study"])); LocusZoom.Layouts.mutate_attrs(layout, '$..data_layers[?(@.tag === "phewas")].tooltip.html', [ "Trait: {{phewas:trait_label|htmlescape}}
", "Trait Category: {{phewas:trait_group|htmlescape}}
", diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index cba10703..c9bc54bc 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -12,6 +12,47 @@ import DataSources from '../../../esm/data'; Test composition of the LocusZoom.Panel object and its base classes */ describe('LocusZoom.DataLayer', function () { + describe('getElementId generates unique IDs for elements', function () { + it('generates unique IDs when id_field is an exact field reference with a value', function () { + const layer = new BaseDataLayer({ id: 'layername', id_field: 'exact_field' }); + let actual = layer.getElementId({ exact_field: 12 }); + assert.equal(actual, 'layername-12', 'Generates ID from exact field value'); + + + assert.throws( + () => layer.getElementId({ not_much_to_go_on: 12 }), + /Unable to generate/, + 'Cannot generate ID field if no value is present for that field' + ); + }); + + it('generates unique IDs when id_field is a valid template expression', function () { + const layer = new BaseDataLayer({ id: 'layername', id_field: 'element_{{some_field}}_{{other_field}}' }); + + let actual = layer.getElementId({ some_field: 'carbon', other_field: '12' }); + assert.equal(actual, 'layername-element_carbon_12', 'Generates ID from template string'); + + layer.layout.id_field = 'partial_{{some_field}}_{{other_field}}'; + actual = layer.getElementId({ some_field: 'carbon' }); + assert.equal(actual, 'layername-partial_carbon_', 'Template values do not warn when some fields are missing (which might be awkward)'); + + layer.layout.id_field = 'neither-template-nor-field'; + assert.throws( + () => layer.getElementId({ some_field: 'carbon', other_field: '12' }), + /Unable to generate/, + 'Not using a field name in the data, and expression is not recognized as a template' + ); + + layer.layout.id_field = ''; + assert.throws( + () => layer.getElementId({ some_field: 'carbon', other_field: '12' }), + /Unable to generate/, + 'An empty template does not evaluate to a useful element ID' + ); + }); + }); + + describe('data contract parsing and validation', function () { beforeEach(function () { const layer = this.layer = new BaseDataLayer({ id: 'something' }); From 503fef1f7d8754376461c17948fcec1a62cd7869 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 20 Sep 2021 21:48:06 -0400 Subject: [PATCH 030/100] Bugfix and cleanup: Use failed-promise cache bugfix; remove references to old fields array; update all datafunction signatures; allow intervals track to use compound id_field to better support tracks with multiple intervals --- esm/components/data_layer/base.js | 5 ----- esm/components/data_layer/highlight_regions.js | 2 +- esm/data/requester.js | 10 ++++++---- esm/ext/lz-aggregation-tests.js | 4 ++-- esm/ext/lz-forest-track.js | 10 ++++------ esm/ext/lz-intervals-enrichment.js | 4 +--- esm/ext/lz-intervals-track.js | 3 +-- examples/interval_annotations.html | 4 ---- examples/interval_enrichment.html | 2 +- examples/js/aggregation-tests-example-page.js | 2 +- examples/phewas_forest.html | 1 - package-lock.json | 6 +++--- package.json | 2 +- .../components/data_layer/test_highlight_regions.js | 2 +- test/unit/components/test_datalayer.js | 4 ---- test/unit/components/test_panel.js | 1 - test/unit/components/test_widgets.js | 1 - test/unit/helpers/test_layouts.js | 4 +--- 18 files changed, 23 insertions(+), 44 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 6a49fae9..89d854ca 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -78,7 +78,6 @@ import SCALABLE from '../../registry/scalable'; * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user. * @memberof module:LocusZoom_DataLayers~BaseDataLayer * @protected - * @type {{type: string, fields: Array, x_axis: {}, y_axis: {}}} */ const default_layout = { id: '', @@ -111,10 +110,6 @@ class BaseDataLayer { * layer that shows association scatter plots, anywhere": even if the IDs are different, the tag can be the same. * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown. * (see: {@link LayoutRegistry.mutate_attrs}) - * @param {String[]} layout.fields A list of (namespaced) fields specifying what data is used by the layer. Only - * these fields will be made available to the data layer, and only data sources (namespaces) referred to in - * this array will be fetched. This represents the "contract" between what data is returned and what data is rendered. - * This fields array works in concert with the data retrieval method BaseAdapter.extractFields. * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely diff --git a/esm/components/data_layer/highlight_regions.js b/esm/components/data_layer/highlight_regions.js index ac89d47e..9d23c3e3 100644 --- a/esm/components/data_layer/highlight_regions.js +++ b/esm/components/data_layer/highlight_regions.js @@ -56,7 +56,7 @@ class HighlightRegions extends BaseDataLayer { throw new Error('highlight_regions layer does not support mouse events'); } - if (layout.regions && layout.regions.length && layout.fields && layout.fields.length) { + if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) { throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both'); } super(...arguments); diff --git a/esm/data/requester.js b/esm/data/requester.js index d204b794..57899a2a 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -20,13 +20,15 @@ class DataOperation { /** * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers. - * It passes state information and ensures that data is formatted in the manner expected by the plot. + * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a + * designated order. + * + * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can + * perform its own calculations, filter results, and apply transforms without influencing other layers. + * (while still respecting a shared cache where appropriate) * * This object is not part of the public interface. It should almost **never** be replaced or modified directly. * - * It is also responsible for constructing a "chain" of dependent requests, by requesting each datasource - * sequentially in the order specified in the datalayer `fields` array. Data sources are only chained within a - * data layer, and only if that layer requests more than one source of data. * @param {DataSources} sources A set of data sources used specifically by this plot instance * @private */ diff --git a/esm/ext/lz-aggregation-tests.js b/esm/ext/lz-aggregation-tests.js index e61344e8..98996f62 100644 --- a/esm/ext/lz-aggregation-tests.js +++ b/esm/ext/lz-aggregation-tests.js @@ -63,7 +63,7 @@ function install (LocusZoom) { return state; } - _getURL(state, chain, fields) { + _getURL(options) { // Unlike most sources, calculations may require access to plot state data even after the initial request // This example source REQUIRES that the external UI widget would store the needed test definitions in a plot state // field called `aggregation_tests` (an object {masks: [], calcs: {}) @@ -219,7 +219,7 @@ function install (LocusZoom) { } } - LocusZoom.DataFunctions.add('gene_plus_aggregation', (genes_data, aggregation_data) => { + LocusZoom.DataFunctions.add('gene_plus_aggregation', ([genes_data, aggregation_data]) => { // Used to highlight genes with significant aggtest results. Unlike a basic left join, it chooses one specific aggtest with the most significant results // Tie the calculated group-test results to genes with a matching name diff --git a/esm/ext/lz-forest-track.js b/esm/ext/lz-forest-track.js index fc339d51..8875e6dc 100644 --- a/esm/ext/lz-forest-track.js +++ b/esm/ext/lz-forest-track.js @@ -210,12 +210,10 @@ function install (LocusZoom) { class CategoryForest extends Forest { _getDataExtent(data, axis_config) { // In a forest plot, the data range is determined by *three* fields (beta + CI start/end) - const ci_config = this.layout.confidence_intervals; - if (ci_config - && this.layout.fields.includes(ci_config.start_field) - && this.layout.fields.includes(ci_config.end_field)) { - const min = (d) => +d[ci_config.start_field]; - const max = (d) => +d[ci_config.end_field]; + const { confidence_intervals } = this.layout; + if (confidence_intervals && confidence_intervals.start_field && confidence_intervals.end_field) { + const min = (d) => +d[confidence_intervals.start_field]; + const max = (d) => +d[confidence_intervals.end_field]; return [d3.min(data, min), d3.max(data, max)]; } diff --git a/esm/ext/lz-intervals-enrichment.js b/esm/ext/lz-intervals-enrichment.js index 2c9b23af..f669d1c8 100644 --- a/esm/ext/lz-intervals-enrichment.js +++ b/esm/ext/lz-intervals-enrichment.js @@ -172,12 +172,11 @@ function install(LocusZoom) { * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions */ const intervals_layer_layout = { - namespace: { 'intervals': 'intervals' }, id: 'intervals_enrichment', type: 'intervals_enrichment', tag: 'intervals_enrichment', + namespace: { 'intervals': 'intervals' }, match: { send: 'intervals:tissueId' }, - fields: ['intervals:chromosome', 'intervals:start', 'intervals:end', 'intervals:pValue', 'intervals:fold', 'intervals:tissueId', 'intervals:ancestry'], id_field: 'intervals:start', // not a good ID field for overlapping intervals start_field: 'intervals:start', end_field: 'intervals:end', @@ -228,7 +227,6 @@ function install(LocusZoom) { type: 'highlight_regions', namespace: { intervals: 'intervals' }, match: { receive: 'intervals:tissueId' }, - fields: ['intervals:start', 'intervals:end', 'intervals:tissueId', 'intervals:ancestry', 'intervals:pValue', 'intervals:fold'], start_field: 'intervals:start', end_field: 'intervals:end', merge_field: 'intervals:tissueId', diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index 21db233b..4dfbb918 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -622,8 +622,7 @@ function install (LocusZoom) { id: 'intervals', type: 'intervals', tag: 'intervals', - fields: ['intervals:start', 'intervals:end', 'intervals:state_id', 'intervals:state_name', 'intervals:itemRgb'], - id_field: 'intervals:start', // FIXME: This is not a good D3 "are these datums redundant" ID for datasets with multiple intervals heavily overlapping + id_field: '{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}', start_field: 'intervals:start', end_field: 'intervals:end', track_split_field: 'intervals:state_name', diff --git a/examples/interval_annotations.html b/examples/interval_annotations.html index e24cd64a..c9410d79 100644 --- a/examples/interval_annotations.html +++ b/examples/interval_annotations.html @@ -94,10 +94,6 @@

Top Hits

} }); - // Remove the itemRgb field from the default layout, because our demo api does not have this information - // (the base layout was defined for the T2D Portal API, which does provide this field) - LocusZoom.Layouts.mutate_attrs(layout, '$..data_layers[?(@.tag === "intervals")].fields', (old_fields) => old_fields.pop() && old_fields); - // This demo is focused on one track (not many), so show the legend. (the intervals track has additional // expand/collapse behavior, so this is controlled in two places instead of one) LocusZoom.Layouts.mutate_attrs(layout, '$..panels[?(@.tag === "intervals")].legend.hidden', false); diff --git a/examples/interval_enrichment.html b/examples/interval_enrichment.html index 4dc73921..632d7241 100644 --- a/examples/interval_enrichment.html +++ b/examples/interval_enrichment.html @@ -69,7 +69,7 @@
< return home
.add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) // There's no special transformation being done to intervals data- just fetch from a URL - .add("intervals", ["BaseApiAdapter", { url: 'data/intervals_enrichment_simplified.json' }]) + .add("intervals", ["BaseLZAdapter", { url: 'data/intervals_enrichment_simplified.json' }]) .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); // Get the standard assocation plot layout from LocusZoom's built-in layouts diff --git a/examples/js/aggregation-tests-example-page.js b/examples/js/aggregation-tests-example-page.js index 45528031..7c370528 100644 --- a/examples/js/aggregation-tests-example-page.js +++ b/examples/js/aggregation-tests-example-page.js @@ -326,7 +326,7 @@ function setupWidgetListeners(plot, aggregationTable, variantsTable, resultStora } }.bind(this)); - LocusZoom.DataFunctions.add('agg_results_table_format', (agg_data, gene_data) => { + LocusZoom.DataFunctions.add('agg_results_table_format', ([agg_data, gene_data]) => { // Aggregation calcs return very complex data. Parse it here, once, into reusable helper objects. const parsed = raremetal.helpers.parsePortalJSON(agg_data); const groups = parsed[0]; diff --git a/examples/phewas_forest.html b/examples/phewas_forest.html index 2a90e8c2..98ab5986 100644 --- a/examples/phewas_forest.html +++ b/examples/phewas_forest.html @@ -128,7 +128,6 @@
< return home
{ shape: "square", class: "lz-data_layer-forest", color: "#b30000", size: 160, label: "10+" } ], id_field: 'phewas:phenotype', - fields: ['phewas:phenotype', 'phewas:log_pvalue', "phewas:log_pvalue|logtoscinotation", 'phewas:beta', 'phewas:ci_start', 'phewas:ci_end'], x_axis: { field: 'phewas:beta', lower_buffer: 0.1, diff --git a/package-lock.json b/package-lock.json index 099e6bcc..d850826e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6687,9 +6687,9 @@ "dev": true }, "undercomplicate": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/undercomplicate/-/undercomplicate-0.1.0.tgz", - "integrity": "sha512-kFQwB6Fhp0muZX2hNTwk5/5eyV9T29npTxAn3NHBsjG5xRzvn1OKm0RspEIJv8fjOOdMbawi93qKe/pom14iGQ==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/undercomplicate/-/undercomplicate-0.1.1.tgz", + "integrity": "sha512-F/rOQ2x3YgH5YcEU+xUKxbv1GGF1znTrBi7MRvqtcDgwubNQ/HGLZbgZh2mHpX0z9AM0T2Pg3zqPwKWYK+VJhQ==", "requires": { "@hapi/topo": "^5.1.0", "just-clone": "^3.2.1" diff --git a/package.json b/package.json index e4becab8..c5f3f089 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "deepmerge": "^4.2.2", "just-clone": "^3.2.1", "tabix-reader": "^1.0.1", - "undercomplicate": "^0.1.0" + "undercomplicate": "^0.1.1" }, "devDependencies": { "@babel/core": "^7.13.1", diff --git a/test/unit/components/data_layer/test_highlight_regions.js b/test/unit/components/data_layer/test_highlight_regions.js index 90d5a27f..0f94c74c 100644 --- a/test/unit/components/data_layer/test_highlight_regions.js +++ b/test/unit/components/data_layer/test_highlight_regions.js @@ -11,7 +11,7 @@ describe('highlight_regions data layer', function () { it('must choose to specify data from regions or fields, but not both', function () { const layout = { regions: [{start: 1, end: 12}], - fields: ['mydata:start', 'mydata:end'], + namespace: { 'mydata': 'mydata' }, }; assert.throws( () => DATA_LAYERS.create('highlight_regions', layout), diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index c9bc54bc..367c48cc 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -605,7 +605,6 @@ describe('LocusZoom.DataLayer', function () { { id: 'd', namespace: { d: 'd' }, - fields: ['d:id'], id_field: 'd:id', type: 'scatter', selected: { onclick: 'toggle' }, @@ -821,7 +820,6 @@ describe('LocusZoom.DataLayer', function () { id: 'd', namespace: { d: 'd'}, type: 'scatter', - fields: ['d:id', 'd:x', 'd:y'], id_field: 'd:id', x_axis: { field: 'd:x' }, y_axis: { field: 'd:y'}, @@ -995,7 +993,6 @@ describe('LocusZoom.DataLayer', function () { { id: 'd', namespace: { d: 'd'}, - fields: ['d:id', 'd:a'], id_field: 'd:id', type: 'scatter', filters: null, @@ -1082,7 +1079,6 @@ describe('LocusZoom.DataLayer', function () { { id: 'd', namespace: { d: 'd'}, - fields: ['d:id', 'd:some_field'], id_field: 'd:id', type: 'scatter', selected: { onclick: 'toggle' }, diff --git a/test/unit/components/test_panel.js b/test/unit/components/test_panel.js index a88eec57..d95fc036 100644 --- a/test/unit/components/test_panel.js +++ b/test/unit/components/test_panel.js @@ -342,7 +342,6 @@ describe('Panel', function() { id: 'd', type: 'scatter', namespace: { static: 'static' }, - fields: ['static:id', 'static:x', 'static:y'], id_field: 'static:id', z_index: 0, x_axis: { diff --git a/test/unit/components/test_widgets.js b/test/unit/components/test_widgets.js index 931f35f2..c5995f7e 100644 --- a/test/unit/components/test_widgets.js +++ b/test/unit/components/test_widgets.js @@ -62,7 +62,6 @@ describe('Toolbar widgets', function () { { id: 'd', namespace: {d: 'd'}, - fields: ['d:id', 'd:a'], id_field: 'd:id', type: 'scatter', }, diff --git a/test/unit/helpers/test_layouts.js b/test/unit/helpers/test_layouts.js index 6786e4b9..4c752666 100644 --- a/test/unit/helpers/test_layouts.js +++ b/test/unit/helpers/test_layouts.js @@ -33,7 +33,7 @@ describe('Layout helper functions', function () { it('finds values inside template syntax', function () { const namespaces = ['assoc']; const layout = { - y_field: 'ld:correlation', // Not listed in namespace = not in fields contract + y_field: 'ld:correlation', text: '{{assoc:nearest_gene}} - {{#if assoc:rsid}} Date: Tue, 21 Sep 2021 17:38:48 -0400 Subject: [PATCH 031/100] Update README usage notes --- README.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5d4a80a6..05871d02 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Here's an example of defining a data sources object for a remote API: ```javascript var data_sources = new LocusZoom.DataSources(); -data_sources.add("assoc", ["AssociationLZ", { url: "http://server.com/api/", params: {source: 1} }]); +data_sources.add("assoc", ["AssociationLZ", { url: "http://server.com/api/", source: 1 }]); ``` The above example adds an "AssociationLZ" data source (a predefined data source designed to make requests for association data) with a defined URL. The namespace for this data source is "assoc". @@ -100,23 +100,22 @@ A basic example may then look like this: -
+
- diff --git a/examples/credible_sets.html b/examples/credible_sets.html index 788fa407..41ab79ee 100644 --- a/examples/credible_sets.html +++ b/examples/credible_sets.html @@ -13,8 +13,7 @@ - - + LocusZoom.js ~ Credible Sets diff --git a/examples/js/aggregation-tests-example-page.js b/examples/js/aggregation-tests-example-page.js index 7c370528..82220063 100644 --- a/examples/js/aggregation-tests-example-page.js +++ b/examples/js/aggregation-tests-example-page.js @@ -32,6 +32,33 @@ const Observable = function () { // Allow UI elements to monitor changes in a va return handle_value; }; +// Format data in a particular way to look good in the tables on this page +const agg_results_table_format = (state, [agg_data, gene_data]) => { + // Aggregation calcs return very complex data. Parse it here, once, into reusable helper objects. + const parsed = raremetal.helpers.parsePortalJSON(agg_data); + const groups = parsed[0]; + const variants = parsed[1]; + + ///////// + // Post-process this data with any annotations required by data tables on this page + + // The aggregation results use the unique ENSEMBL ID for a gene. The gene source tells us how to connect + // that to a human-friendly gene name (as displayed in the LZ plot) + const _genes_lookup = {}; + gene_data.forEach(function (gene) { + const gene_id = gene.gene_id.split('.')[0]; // Ignore ensembl version on gene ids + _genes_lookup[gene_id] = gene.gene_name; + }); + groups.data.forEach(function (one_result) { + const this_group = groups.getOne(one_result.mask, one_result.group); + // Add synthetic fields that are not part of the raw calculation results + one_result.group_display_name = _genes_lookup[one_result.group] || one_result.group; + one_result.variant_count = this_group.variants.length; + }); + return [groups, variants]; +}; +LocusZoom.DataFunctions.add('agg_results_table_format', agg_results_table_format); + // Make a custom layout object function customizePlotLayout(layout) { // Customize an existing plot layout with the data for aggregation tests @@ -325,32 +352,6 @@ function setupWidgetListeners(plot, aggregationTable, variantsTable, resultStora aggregationTable.tableClearFilter(gene_column_name, selected_gene); } }.bind(this)); - - LocusZoom.DataFunctions.add('agg_results_table_format', ([agg_data, gene_data]) => { - // Aggregation calcs return very complex data. Parse it here, once, into reusable helper objects. - const parsed = raremetal.helpers.parsePortalJSON(agg_data); - const groups = parsed[0]; - const variants = parsed[1]; - - ///////// - // Post-process this data with any annotations required by data tables on this page - - // The aggregation results use the unique ENSEMBL ID for a gene. The gene source tells us how to connect - // that to a human-friendly gene name (as displayed in the LZ plot) - const _genes_lookup = {}; - gene_data.forEach(function (gene) { - const gene_id = gene.gene_id.split('.')[0]; // Ignore ensembl version on gene ids - _genes_lookup[gene_id] = gene.gene_name; - }); - groups.data.forEach(function (one_result) { - const this_group = groups.getOne(one_result.mask, one_result.group); - // Add synthetic fields that are not part of the raw calculation results - one_result.group_display_name = _genes_lookup[one_result.group] || one_result.group; - one_result.variant_count = this_group.variants.length; - }); - return [groups, variants]; - }); - plot.subscribeToData( { namespace: {'aggregation': 'aggregation', 'gene': 'gene' }, diff --git a/package-lock.json b/package-lock.json index 814fa302..d8717c82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2697,11 +2697,6 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" - }, "default-require-extensions": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", @@ -3560,6 +3555,11 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "gwas-credible-sets": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/gwas-credible-sets/-/gwas-credible-sets-0.1.0.tgz", + "integrity": "sha512-CQnvmUV/Xg/xAfx0/M0rTUD7uhnhmwsTF7I6/kJeHGRTbx95RQfdBQW2ByNnbL6OcJOnZRpFpJf/P2keWhivhg==" + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", diff --git a/package.json b/package.json index 5b794156..67b7c5b3 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "d3": "^5.16.0", - "deepmerge": "^4.2.2", + "gwas-credible-sets": "^0.1.0", "just-clone": "^3.2.1", "tabix-reader": "^1.0.1", "undercomplicate": "^0.1.1" diff --git a/webpack.common.cjs b/webpack.common.cjs index adf1bba3..5a897992 100644 --- a/webpack.common.cjs +++ b/webpack.common.cjs @@ -83,8 +83,6 @@ module.exports = { externals: { d3: 'd3', locuszoom: 'LocusZoom', - 'gwas-credible-sets': 'gwasCredibleSets', - 'tabix-reader': 'tabix', 'raremetal.js': 'raremetal', }, }; From 6c3c6155656351cdd43c554fd8403c7a4942f570 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 4 Oct 2021 17:03:00 -0400 Subject: [PATCH 046/100] Update LZ examples: Remember user-selected LD refvar in URL --- examples/coaccessibility.html | 2 +- examples/credible_sets.html | 2 +- examples/gwas_catalog.html | 2 +- examples/js/aggregation-tests-example-page.js | 2 +- index.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/coaccessibility.html b/examples/coaccessibility.html index e41092e3..21216c8b 100644 --- a/examples/coaccessibility.html +++ b/examples/coaccessibility.html @@ -97,7 +97,7 @@
< return home
.add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); // Get the standard association plot layout from LocusZoom's built-in layouts - var stateUrlMapping = { chr: "chrom", start: "start", end: "end" }; + var stateUrlMapping = { chr: "chrom", start: "start", end: "end", ldrefvar: 'ld_variant' }; // Fetch initial position from the URL, or use some defaults var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { diff --git a/examples/credible_sets.html b/examples/credible_sets.html index 41ab79ee..8187b668 100644 --- a/examples/credible_sets.html +++ b/examples/credible_sets.html @@ -101,7 +101,7 @@

Top Hits

Define and render the plot */ // Fetch custom layout defined for usage with credible sets - var stateUrlMapping = {chr: "chrom", start: "start", end: "end"}; + var stateUrlMapping = {chr: "chrom", start: "start", end: "end", ldrefvar: 'ld_variant' }; // Fetch initial position from the URL, or use some defaults var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { diff --git a/examples/gwas_catalog.html b/examples/gwas_catalog.html index b52f4da4..f7a7ff7f 100644 --- a/examples/gwas_catalog.html +++ b/examples/gwas_catalog.html @@ -96,7 +96,7 @@

Top Hits

Define and render the plot */ // Fetch custom layout defined for usage with credible sets - var stateUrlMapping = {chr: "chrom", start: "start", end: "end"}; + var stateUrlMapping = {chr: "chrom", start: "start", end: "end", ldrefvar: 'ld_variant' }; // Fetch initial position from the URL, or use some defaults var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { diff --git a/examples/js/aggregation-tests-example-page.js b/examples/js/aggregation-tests-example-page.js index 82220063..426e47f5 100644 --- a/examples/js/aggregation-tests-example-page.js +++ b/examples/js/aggregation-tests-example-page.js @@ -237,7 +237,7 @@ function createDisplayWidgets(label_store, context) { build: 'GRCh37', }]); - const stateUrlMapping = { chr: 'chrom', start: 'start', end: 'end' }; + const stateUrlMapping = { chr: 'chrom', start: 'start', end: 'end', ldrefvar: 'ld_variant' }; let initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { initialState = { chr: '19', start: 45312079, end: 45512079 }; diff --git a/index.html b/index.html index c45b4912..218ec78a 100644 --- a/index.html +++ b/index.html @@ -218,7 +218,7 @@
Multiple Phenotypes (Layered)
} // Get the standard association plot layout from LocusZoom's built-in layouts - var stateUrlMapping = {chr: "chrom", start: "start", end: "end"}; + var stateUrlMapping = {chr: "chrom", start: "start", end: "end", ldrefvar: 'ld_variant' }; // Fetch initial position from the URL, or use some defaults var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { From 4b85d2ff9e3eca5e44438d678acb0b4b477296a7 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 4 Oct 2021 18:00:42 -0400 Subject: [PATCH 047/100] Reorganize extension demos into `ext/` folder --- esm/ext/lz-intervals-track.js | 1 - examples/{ => ext}/aggregation_tests.html | 12 ++++++------ examples/{ => ext}/aggregation_tests.png | Bin examples/{ => ext}/credible_sets.html | 10 +++++----- examples/{ => ext}/credible_sets.png | Bin examples/{ => ext}/interval_annotations.html | 10 +++++----- examples/{ => ext}/interval_annotations.png | Bin examples/{ => ext}/interval_enrichment.html | 12 ++++++------ examples/{ => ext}/phewas_forest.html | 8 ++++---- examples/{ => ext}/phewas_forest.png | Bin examples/template.html | 4 ++-- index.html | 16 ++++++++-------- 12 files changed, 36 insertions(+), 37 deletions(-) rename examples/{ => ext}/aggregation_tests.html (95%) rename examples/{ => ext}/aggregation_tests.png (100%) rename examples/{ => ext}/credible_sets.html (93%) rename examples/{ => ext}/credible_sets.png (100%) rename examples/{ => ext}/interval_annotations.html (92%) rename examples/{ => ext}/interval_annotations.png (100%) rename examples/{ => ext}/interval_enrichment.html (85%) rename examples/{ => ext}/phewas_forest.html (96%) rename examples/{ => ext}/phewas_forest.png (100%) diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index 301f1439..764515b5 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -716,7 +716,6 @@ function install (LocusZoom) { max_region_scale: 1000000, toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'), panels: [ - LocusZoom.Layouts.get('panel', 'association'), LocusZoom.Layouts.get('panel', 'association'), LocusZoom.Layouts.merge({ min_height: 120, height: 120 }, intervals_panel_layout), LocusZoom.Layouts.get('panel', 'genes'), diff --git a/examples/aggregation_tests.html b/examples/ext/aggregation_tests.html similarity index 95% rename from examples/aggregation_tests.html rename to examples/ext/aggregation_tests.html index 68bae47e..62a1af8d 100644 --- a/examples/aggregation_tests.html +++ b/examples/ext/aggregation_tests.html @@ -10,10 +10,10 @@ - + - - + + - + LocusZoom.js ~ Aggregation Tests @@ -63,7 +63,7 @@

LocusZoom.js

Aggregation Tests Demonstration

-
< return home
+
< return home

@@ -196,7 +196,7 @@

References

- + - - + + - + - + LocusZoom.js ~ Credible Sets @@ -42,7 +42,7 @@

LocusZoom.js

Credible Sets Demonstration

-
< return home
+
< return home

diff --git a/examples/credible_sets.png b/examples/ext/credible_sets.png similarity index 100% rename from examples/credible_sets.png rename to examples/ext/credible_sets.png diff --git a/examples/interval_annotations.html b/examples/ext/interval_annotations.html similarity index 92% rename from examples/interval_annotations.html rename to examples/ext/interval_annotations.html index c9410d79..930d0e66 100644 --- a/examples/interval_annotations.html +++ b/examples/ext/interval_annotations.html @@ -7,12 +7,12 @@ - - - + + + - + LocusZoom.js ~ Interval Annotations Example @@ -35,7 +35,7 @@

LocusZoom.js

Interval Annotations Example

-
< return home
+
< return home

diff --git a/examples/interval_annotations.png b/examples/ext/interval_annotations.png similarity index 100% rename from examples/interval_annotations.png rename to examples/ext/interval_annotations.png diff --git a/examples/interval_enrichment.html b/examples/ext/interval_enrichment.html similarity index 85% rename from examples/interval_enrichment.html rename to examples/ext/interval_enrichment.html index 632d7241..7b18db82 100644 --- a/examples/interval_enrichment.html +++ b/examples/ext/interval_enrichment.html @@ -7,12 +7,12 @@ - - - + + + - + LocusZoom.js ~ Interval Enrichment Example @@ -35,7 +35,7 @@

LocusZoom.js

Interval Annotations Example

-
< return home
+
< return home

@@ -69,7 +69,7 @@
< return home
.add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) // There's no special transformation being done to intervals data- just fetch from a URL - .add("intervals", ["BaseLZAdapter", { url: 'data/intervals_enrichment_simplified.json' }]) + .add("intervals", ["BaseLZAdapter", { url: '../data/intervals_enrichment_simplified.json' }]) .add("constraint", ["GeneConstraintLZ", { url: "https://gnomad.broadinstitute.org/api/", build: 'GRCh37' }]); // Get the standard assocation plot layout from LocusZoom's built-in layouts diff --git a/examples/phewas_forest.html b/examples/ext/phewas_forest.html similarity index 96% rename from examples/phewas_forest.html rename to examples/ext/phewas_forest.html index 98ab5986..e87381c4 100644 --- a/examples/phewas_forest.html +++ b/examples/ext/phewas_forest.html @@ -7,11 +7,11 @@ - + - + - + LocusZoom.js ~ PheWAS (Forest) Example @@ -34,7 +34,7 @@

LocusZoom.js

PheWAS (Forest) Example

-
< return home
+
< return home

diff --git a/examples/phewas_forest.png b/examples/ext/phewas_forest.png similarity index 100% rename from examples/phewas_forest.png rename to examples/ext/phewas_forest.png diff --git a/examples/template.html b/examples/template.html index d1322ae6..22c2b638 100644 --- a/examples/template.html +++ b/examples/template.html @@ -7,9 +7,9 @@ - - + + LocusZoom.js ~ Template Example ~ Gallery diff --git a/index.html b/index.html index 218ec78a..495b5030 100644 --- a/index.html +++ b/index.html @@ -126,15 +126,15 @@

Example Plots

@@ -147,15 +147,15 @@
PheWAS (Scatter)
From c279b54c5dd9ca498bde26f3ef3d7958b5efae38 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 5 Oct 2021 16:30:27 -0400 Subject: [PATCH 048/100] New examples and improvements - New "BED file" layout for standard bed files (not specific to chromHMM) - MVP tabix_tracks demo showing GWAS and BED files from tabix with standard lzParsers fields - More consistent parser constructors with options-arg - Remove references to unused legacy adapter param - Move demos of extension features to an ext folder inside examples/ --- esm/ext/lz-intervals-track.js | 47 +++++- esm/ext/lz-parsers/bed.js | 3 +- esm/ext/lz-parsers/ld.js | 2 +- examples/coaccessibility.html | 9 +- examples/ext/credible_sets.html | 1 - examples/ext/interval_annotations.html | 2 +- examples/ext/interval_enrichment.html | 2 +- examples/ext/tabix_tracks.html | 168 ++++++++++++++++++++++ examples/gwas_catalog.html | 2 +- examples/misc/covariates_model.html | 2 +- examples/multiple_phenotypes_layered.html | 2 +- examples/template.html | 2 +- test/unit/ext/lz-parsers/test_bed.js | 10 +- test/unit/ext/lz-parsers/test_plink_ld.js | 4 +- 14 files changed, 228 insertions(+), 28 deletions(-) create mode 100644 examples/ext/tabix_tracks.html diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index 764515b5..ff2fb224 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -373,6 +373,7 @@ function install (LocusZoom) { this.layout.legend = known_categories.map(function (pair, index) { const id = pair[0]; const label = pair[1]; + // FIXME: use resolveScalableParameter here, so that itemRgb mode shows correct colors const item_color = color_scale.parameters.values[index]; const item = { shape: 'rect', width: 9, label: label, color: item_color }; item[self.layout.track_split_field] = id; @@ -612,7 +613,9 @@ function install (LocusZoom) { /** * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for chromHMM output, - * in which various states are assigned numeric state IDs and (<= as many) text state names + * in which various states are assigned numeric state IDs and (<= as many) text state names. + * + * This layout is deprecated; most usages would be better served by the bed_intervals_layer layout instead. * @alias module:LocusZoom_Layouts~intervals_layer * @type data_layer * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions @@ -665,8 +668,31 @@ function install (LocusZoom) { tooltip: intervals_tooltip_layout, }; + /** - * (**extension**) A panel containing an intervals data layer, eg for BED tracks + * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for standard BED3+ files and the field names emitted by the LzParsers extension. + * @alias module:LocusZoom_Layouts~bed_intervals_layer + * @type data_layer + * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions + */ + const bed_intervals_layer_layout = LocusZoom.Layouts.merge({ + id_field: '{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}', + start_field: 'intervals:chromStart', + end_field: 'intervals:chromEnd', + track_split_field: 'intervals:name', + track_label_field: 'intervals:name', + split_tracks: true, + always_hide_legend: false, + tooltip: LocusZoom.Layouts.merge({ + html: `{{intervals:name|htmlescape}}
{{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}} +{{#if intervals:score}}
Score: {{intervals:score|htmlescape}}{{/if}} +`, + }, intervals_tooltip_layout), + }, intervals_layer_layout); + + + /** + * (**extension**) A panel containing an intervals data layer, eg for BED tracks. This is a legacy layout whose field names were specific to one partner site. * @alias module:LocusZoom_Layouts~intervals * @type panel * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions @@ -701,9 +727,22 @@ function install (LocusZoom) { data_layers: [intervals_layer_layout], }; + /** + * (**extension**) A panel containing an intervals data layer, eg for BED tracks. These field names match those returned by the LzParsers extension. + * @alias module:LocusZoom_Layouts~bed_intervals + * @type panel + * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions + */ + const bed_intervals_panel_layout = LocusZoom.Layouts.merge({ + // Normal BED tracks show the panel legend in collapsed mode! + min_height: 120, + height: 120, + data_layers: [bed_intervals_layer_layout], + }, intervals_panel_layout); + /** * (**extension**) A plot layout that shows association summary statistics, genes, and interval data. This example assumes - * chromHMM data. (see panel layout) + * chromHMM data. (see panel layout) Few people will use the full intervals plot layout directly outside of an example. * @alias module:LocusZoom_Layouts~interval_association * @type plot * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions @@ -727,7 +766,9 @@ function install (LocusZoom) { LocusZoom.Layouts.add('tooltip', 'standard_intervals', intervals_tooltip_layout); LocusZoom.Layouts.add('data_layer', 'intervals', intervals_layer_layout); + LocusZoom.Layouts.add('data_layer', 'bed_intervals', bed_intervals_layer_layout); LocusZoom.Layouts.add('panel', 'intervals', intervals_panel_layout); + LocusZoom.Layouts.add('panel', 'bed_intervals', bed_intervals_panel_layout); LocusZoom.Layouts.add('plot', 'interval_association', intervals_plot_layout); LocusZoom.ScaleFunctions.add('to_rgb', to_rgb); diff --git a/esm/ext/lz-parsers/bed.js b/esm/ext/lz-parsers/bed.js index 5e20d9a1..3c18a133 100644 --- a/esm/ext/lz-parsers/bed.js +++ b/esm/ext/lz-parsers/bed.js @@ -29,7 +29,7 @@ function _hasNum(value) { * @param {Boolean} normalize Whether to normalize the output to the format expected by LocusZoom (eg type coercion * for numbers, removing chr chromosome prefixes, and using 1-based and inclusive coordinates instead of 0-based disjoint intervals) */ -function makeBed12Parser(normalize = true) { +function makeBed12Parser({normalize = true}) { /* * @param {String} line The line of text to be parsed */ @@ -71,7 +71,6 @@ function makeBed12Parser(normalize = true) { thickEnd = _hasNum(thickEnd); itemRgb = _bedMissing(itemRgb); - itemRgb = itemRgb ? `rgb(${itemRgb})` : itemRgb; // LocusZoom doesn't use these fields for rendering. Parsing below is theoretical/best-effort. blockCount = _hasNum(blockCount); diff --git a/esm/ext/lz-parsers/ld.js b/esm/ext/lz-parsers/ld.js index b8813a5f..320c62df 100644 --- a/esm/ext/lz-parsers/ld.js +++ b/esm/ext/lz-parsers/ld.js @@ -12,7 +12,7 @@ import {normalizeMarker} from '../../helpers/parse'; * * @returns {Object} Same column names used by the UM LD Server */ -function makePlinkLdParser(normalize = true) { +function makePlinkLdParser({normalize = true}) { return (line) => { // Sample headers are below: SNP_A and SNP_B are based on ID column of the VCF // CHR_A BP_A SNP_A CHR_B BP_B SNP_B R2 diff --git a/examples/coaccessibility.html b/examples/coaccessibility.html index 21216c8b..aa5014c9 100644 --- a/examples/coaccessibility.html +++ b/examples/coaccessibility.html @@ -71,16 +71,9 @@
< return home
+ + + + + + + + LocusZoom.js ~ Tabix Tracks + + + + + + +
+ +

LocusZoom.js

+ +

Tabix tracks

+
< return home
+ +
+ +

+ LocusZoom.js is able to fetch data directly from tabix files. This is very helpful if you want to render a plot from + your own data, without transforming the files into an intermediate format (like creating JSON files or loading + the data into an API first). This is particularly important for very large datasets, because it allows interactive + region-based queries, without having to transfer the entire dataset to the user's web browser first. + Only the index file is loaded, plus the data needed for a particular region of interest. +

+

+ LocusZoom provides an extension to help parse tabix data files for several common data formats. Additional + parsers and index types may be supported in the future. Working directly with tabix files is a way to get + started with LocusZoom fast as it does not require building or maintaining a server backend. It is helpful for + sharing small numbers of datasets or in groups that don't want to maintain their own infrastructure. + Typically, larger data sharing portals will not use this approach; as the number of datasets grow, they will + want to run their own web server to support features like searching, complex queries, and harmonizing many + datasets into a single standard format. Tools like PheWeb are + a popular way to handle this sort of use case. +

+

+ A key feature of LocusZoom is that each track is independent. This means it is very straightforward to define + layouts in which some tracks come from a tabix file (like a BED track), while others are fetched from a remote + web server that handles standard well-known dataets (like genes or 1000G LD). +

+ +
+ +

Data formatting guidelines

+

+ Below are some tips on formatting your data files. If you are using a static file storage provider like Amazon + S3 or Google Cloud Storage, note that you may need to configure some additional request headers + before Tabix will work properly. +

+ +

GWAS Summary statistics

+

+ There is no single standard for GWAS summary statistics. As such, the LocusZoom.js parser exposes many sets of + options for how to read the data. The general instructions from my.locuszoom.org + are a useful starting point, since the same basic parsing logic is shared across multiple tools. +

+ +

BED files

+

+ BED files are a standard format with 3-12 columns of data. + The default LocusZoom panel layout for a BED track will use chromosome, position, line name, and (optionally) color + to draw the plot. Score will be shown as a tooltip field (if present); it may have a different meaning and scale + depending on the contents of your BED file. As with any LocusZoom track, custom layouts can be created to render data in different ways, or to use more or + fewer columns based on the data of interest. +

+ +

+ The following command is helpful for preparing BED files for use in the browser: + $ sort -k1,1 -k2,2n input.bed | bgzip > input-sorted.gff.gz && tabix -p bed input-sorted.gff.gz
+ Some BED files will have one or more header rows; in this case, modify the tabix command with: --skip-lines N (where N is the number of headers). +

+ +

PLINK LD

+ + +
+ +
+ +
+
+ + + + diff --git a/examples/gwas_catalog.html b/examples/gwas_catalog.html index f7a7ff7f..9762c469 100644 --- a/examples/gwas_catalog.html +++ b/examples/gwas_catalog.html @@ -85,7 +85,7 @@

Top Hits

*/ var apiBase = "//portaldev.sph.umich.edu/api/v1/"; var data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", { url: apiBase + "statistic/single/", source: 45, id_field: "variant" }]) + .add("assoc", ["AssociationLZ", { url: apiBase + "statistic/single/", source: 45 }]) .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', population: 'ALL' }]) .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/" }]) .add("catalog", ["GwasCatalogLZ", { url: apiBase + 'annotation/gwascatalog/results/' }]) diff --git a/examples/misc/covariates_model.html b/examples/misc/covariates_model.html index 3c214782..9c8984a5 100644 --- a/examples/misc/covariates_model.html +++ b/examples/misc/covariates_model.html @@ -67,7 +67,7 @@

Top Hits

// Define Data Sources var apiBase = "https://portaldev.sph.umich.edu/api/v1/"; var data_sources = new LocusZoom.DataSources() - .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", source: 45, id_field: "variant" }]) + .add("assoc", ["AssociationLZ", {url: apiBase + "statistic/single/", source: 45 }]) .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', build: 'GRCh37', population: 'ALL' }]) .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]) .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) diff --git a/examples/multiple_phenotypes_layered.html b/examples/multiple_phenotypes_layered.html index 27df8676..6bdc43e4 100644 --- a/examples/multiple_phenotypes_layered.html +++ b/examples/multiple_phenotypes_layered.html @@ -104,7 +104,7 @@

Top Hits

{ namespace: "cholesterol", title: "Total cholesterol meta-analysis", color: "rgb(53, 126, 189)", study_id: 30 } ]; phenos.forEach(function(pheno){ - data_sources.add(pheno.namespace, ["AssociationLZ", {url: apiBase + "statistic/single/", source: pheno.study_id, id_field: "variant" }]); + data_sources.add(pheno.namespace, ["AssociationLZ", {url: apiBase + "statistic/single/", source: pheno.study_id }]); var association_data_layer_mods = { join_options: [], id: "associationpvalues_" + pheno.namespace, diff --git a/examples/template.html b/examples/template.html index 22c2b638..f7238f9b 100644 --- a/examples/template.html +++ b/examples/template.html @@ -37,7 +37,7 @@
< return home

Template Description

-
+

diff --git a/test/unit/ext/lz-parsers/test_bed.js b/test/unit/ext/lz-parsers/test_bed.js index 697bf422..4f4946fd 100644 --- a/test/unit/ext/lz-parsers/test_bed.js +++ b/test/unit/ext/lz-parsers/test_bed.js @@ -17,7 +17,7 @@ chr7 127480532 127481699 Neg4 0 - 127480532 127481699 0,0,255`.split('\n'); }); it('defaults to normalizing the parsed data to match expectations', function () { - const parser = makeBed12Parser(true); + const parser = makeBed12Parser({normalize: true}); const result = parser(this.lines[0]); const expected = { 'blockCount': null, @@ -37,12 +37,12 @@ chr7 127480532 127481699 Neg4 0 - 127480532 127481699 0,0,255`.split('\n'); }); it('warns if required fields are missing', function () { - const parser = makeBed12Parser(true); + const parser = makeBed12Parser({normalize: true}); assert.throws(() => parser('chr12\t51'), /must provide all required/); }); it('returns raw file contents if normalize = false', function () { - const parser = makeBed12Parser(false); + const parser = makeBed12Parser({normalize: false}); const result = parser(this.lines[0]); const expected = { 'blockCount': undefined, @@ -62,7 +62,7 @@ chr7 127480532 127481699 Neg4 0 - 127480532 127481699 0,0,255`.split('\n'); }); it('handles additional optional BED fields if they are present', function () { - const parser = makeBed12Parser(true); + const parser = makeBed12Parser({normalize: true}); // The trailing comma in a list-field is part of the UCSC BED examples; weird, but make sure that we handle it! const a_line = `chr7 127472363 127473530 Pos2 0 + 127472363 127473530 255,0,0 2 567,488, 0,3512`; const result = parser(a_line); @@ -75,7 +75,7 @@ chr7 127480532 127481699 Neg4 0 - 127480532 127481699 0,0,255`.split('\n'); }); it('performs basic validation of block information', function () { - const parser = makeBed12Parser(true); + const parser = makeBed12Parser({normalize: true}); // Here, blockSizes has 3 items, but blockCounts calls for 2 const a_line = `chr7 127472363 127473530 Pos2 0 + 127472363 127473530 255,0,0 2 567,488,999 0,3512`; assert.throws(() => parser(a_line), /same number of items/); diff --git a/test/unit/ext/lz-parsers/test_plink_ld.js b/test/unit/ext/lz-parsers/test_plink_ld.js index 30daafe1..05f3d393 100644 --- a/test/unit/ext/lz-parsers/test_plink_ld.js +++ b/test/unit/ext/lz-parsers/test_plink_ld.js @@ -5,7 +5,7 @@ import {makePlinkLdParser} from '../../../../esm/ext/lz-parsers/ld'; describe('LD format parsing', function () { it('parses PLINK format LD (raw)', function () { const line = '22\t37470224\t22-37470224-T-C\t22\t37370297\t22-37370297-T-C\t0.000178517'; - const parser = makePlinkLdParser(false); + const parser = makePlinkLdParser({normalize: false}); const actual = parser(line); const expected = { @@ -23,7 +23,7 @@ describe('LD format parsing', function () { it('normalizes chromosome names and variant formats', function () { const line = 'chrx\t37470224\t22-37470224\tchr22\t37370297\tchr22:37370297-T-C\t0.000178517'; - const parser = makePlinkLdParser(true); + const parser = makePlinkLdParser({normalize: true}); const actual = parser(line); const expected = { From 88a4ffe1a0b7fcc0739924d400e8f71ba4b1aca1 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 5 Oct 2021 17:16:57 -0400 Subject: [PATCH 049/100] Bugfix for interval track colors in explicit itemRgb mode --- esm/ext/lz-intervals-track.js | 5 ++--- test/unit/ext/lz-parsers/test_bed.js | 2 +- test/unit/ext/test_ext_intervals-track.js | 16 ++++++++-------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index ff2fb224..8bcd5e99 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -373,7 +373,6 @@ function install (LocusZoom) { this.layout.legend = known_categories.map(function (pair, index) { const id = pair[0]; const label = pair[1]; - // FIXME: use resolveScalableParameter here, so that itemRgb mode shows correct colors const item_color = color_scale.parameters.values[index]; const item = { shape: 'rect', width: 9, label: label, color: item_color }; item[self.layout.track_split_field] = id; @@ -544,10 +543,10 @@ function install (LocusZoom) { // Choose an appropriate color scheme based on the number of items in the track, and whether or not we are // using explicitly provided itemRgb information _makeColorScheme(category_info) { - // If at least one element has an explicit itemRgb, assume the entire dataset has colors + // If at least one element has an explicit itemRgb, assume the entire dataset has colors. BED intervals require rgb triplets,so assume that colors will always be "r,g,b" format. const has_explicit_colors = category_info.find((item) => item[2]); if (has_explicit_colors) { - return category_info.map((item) => item[2]); + return category_info.map((item) => to_rgb({}, item[2])); } // Use a set of color schemes for common 15, 18, or 25 state models, as specified from: diff --git a/test/unit/ext/lz-parsers/test_bed.js b/test/unit/ext/lz-parsers/test_bed.js index 4f4946fd..109162fc 100644 --- a/test/unit/ext/lz-parsers/test_bed.js +++ b/test/unit/ext/lz-parsers/test_bed.js @@ -26,7 +26,7 @@ chr7 127480532 127481699 Neg4 0 - 127480532 127481699 0,0,255`.split('\n'); 'chrom': '7', 'chromEnd': 127472363, 'chromStart': 127471197, - 'itemRgb': 'rgb(255,0,0)', + 'itemRgb': '255,0,0', 'name': 'Pos1', 'score': 0, 'strand': '+', diff --git a/test/unit/ext/test_ext_intervals-track.js b/test/unit/ext/test_ext_intervals-track.js index 42894129..04cac5d2 100644 --- a/test/unit/ext/test_ext_intervals-track.js +++ b/test/unit/ext/test_ext_intervals-track.js @@ -68,10 +68,10 @@ describe('Interval annotation track', function () { this.instance.data = [ // Interesting property of real data: sometimes two HMM models are assigned the same label. // State id is considered the unambiguous identifier. - { 'intervals:state_name': 'Strong enhancer', 'intervals:state_id': 1, 'intervals:itemRgb': '#FF0000' }, - { 'intervals:state_name': 'Weak enhancer', 'intervals:state_id': 2, 'intervals:itemRgb': '#00FF00' }, - { 'intervals:state_name': 'Strong enhancer', 'intervals:state_id': 1, 'intervals:itemRgb': '#FF0000' }, - { 'intervals:state_name': 'Strong enhancer', 'intervals:state_id': 3, 'intervals:itemRgb': '#0000FF' }, + { 'intervals:state_name': 'Strong enhancer', 'intervals:state_id': 1, 'intervals:itemRgb': '255,0,0' }, + { 'intervals:state_name': 'Weak enhancer', 'intervals:state_id': 2, 'intervals:itemRgb': '0,255,0' }, + { 'intervals:state_name': 'Strong enhancer', 'intervals:state_id': 1, 'intervals:itemRgb': '255,0,0' }, + { 'intervals:state_name': 'Strong enhancer', 'intervals:state_id': 3, 'intervals:itemRgb': '0,0,255' }, ]; this.instance._applyLayoutOptions(); @@ -81,13 +81,13 @@ describe('Interval annotation track', function () { const final_legend = this.instance.layout.legend; const final_colors = find_color_options(this.instance.layout); assert.deepEqual(final_colors.parameters.categories, [1, 2, 3], 'Unique categories are generated'); - assert.deepEqual(final_colors.parameters.values, ['#FF0000', '#00FF00', '#0000FF'], 'Unique color options are tracked'); + assert.deepEqual(final_colors.parameters.values, ['rgb(255,0,0)', 'rgb(0,255,0)', 'rgb(0,0,255)'], 'Unique color options are tracked'); assert.deepEqual( final_legend, [ - { color: '#FF0000', 'intervals:state_id': 1, label: 'Strong enhancer', shape: 'rect', 'width': 9 }, - { color: '#00FF00', 'intervals:state_id': 2, label: 'Weak enhancer', shape: 'rect', 'width': 9 }, - { color: '#0000FF', 'intervals:state_id': 3, label: 'Strong enhancer', shape: 'rect', 'width': 9 }, + { color: 'rgb(255,0,0)', 'intervals:state_id': 1, label: 'Strong enhancer', shape: 'rect', 'width': 9 }, + { color: 'rgb(0,255,0)', 'intervals:state_id': 2, label: 'Weak enhancer', shape: 'rect', 'width': 9 }, + { color: 'rgb(0,0,255)', 'intervals:state_id': 3, label: 'Strong enhancer', shape: 'rect', 'width': 9 }, ], 'Legend items map the correct stateID and colors together' ); From 4a2aee2cec759d1fd4709da4fbc82dbf16eff9ed Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 6 Oct 2021 15:22:31 -0400 Subject: [PATCH 050/100] Add preparation instructions for PLINK-1.9 1000G demo near the demo region for the plot --- examples/ext/tabix_tracks.html | 66 +++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/examples/ext/tabix_tracks.html b/examples/ext/tabix_tracks.html index 84358aca..99d4deb2 100644 --- a/examples/ext/tabix_tracks.html +++ b/examples/ext/tabix_tracks.html @@ -96,7 +96,71 @@

BED files

PLINK LD

- +

+ Linkage Disequilibrium is an important tool for interpreting LocusZoom.js plots. In order to support viewing any + region, most LocusZoom.js usages take advantage of the Michigan LD server to calculate region-based LD relative + to a particular reference variant, based on the well-known 1000G reference panel. +

+

+ We recognize that the 1000G reference panel (and its sub-populations) is not suited to all cases, especially for + studies with ancestry-specific results or large numbers of rare variants not represented in a public panel. + For many groups, setting up a private LD Server instance is not an option. + As a fallback, we support parsing a file format derived from PLINK 1.9 `--ld-snp` calculations. Instructions + for preparing these files are provided below. Due to the potential for very large output files, we only + support pre-calculated LD relative to one (or a few) LD reference variants; this means that this feature + requires some advance knowledge of interesting regions in order to be useful. If the user views any + region that is not near a pre-provided reference variant, they will see grey dots indicating the absence of LD information. + We have intentionally restricted the demo so that this limitation is clear. +

+ +
+
Preparing genotype files: harmonizing ID formats
+
+ LocusZoom typically calculates LD relative to a variant by EPACTS-format specifier (chrom:pos_ref/alt). + However, genotype VCF files have no single standard for how variants are identified. Some files even use a different variant format for each variant, which makes it hard to write easy copy-and-paste commands that would work widely + across files. Your file can be transformed to match the tutorial assumptions via common tool and the command below: + + bcftools annotate -Oz --set-id '%CHROM\:%POS\_%REF\/%ALT' original_vcf.gz > vcfname_epacts_id_format.gz + + In some rare cases (such as 1000G phase 3), data preparation errors may result in duplicate entries for the same variant. This can break PLINK. A command such as the one below can be used to find these duplicates:
+ zcat < vcfname_epacts_id_format.gz | cut -f3 | sort | uniq -d
+ They can then be removed using the following command (check the output carefully before using, because reasons for duplicate lines vary widely): +

+ bcftools norm -Oz --rm-dup all vcfname_epacts_id_format.gz > vcfname_epacts_id_format_rmdups.gz +
+ +
Calculating LD relative to reference variants
+
+ The command below will calculate LD relative to (several) variants in a 500 kb region centered around each reference variant.
+ plink-1.9 --r2 --vcf vcfname_epacts_id_format.gz --ld-window 499999 --ld-window-kb 500 --ld-window-r2 0.0 --ld-snp-list mysnplist.txt + + This command assumes the presence of a file named mysnplist.txt, which contains a series of rows like the example below:
+ + 16:53842908_G/A + 16:53797908_C/G + 16:53809247_G/A + +
+ +
Preparing the LD output file for use with LocusZoom.js
+
+ As of this writing, the desired LD functionality requires PLINK 1.9.x (and is not yet available in newer versions) + Unfortunately, PLINK's default output format is not compatible with tabix, for historical reasons. + Transform to a format readable by LocusZoom via the following sequence of commands. + cat plink.ld | tail -n+2 | sed 's/^[[:space:]]*//g' | sed 's/[[:space:]]*$//g' | tr -s ' ' '\t' | sort -k4,4 -k5,5n | bgzip > plink.ld.tab.gz && tabix -s4 -b5 -e5 plink.ld.tab.gz +
+ +
I don't use PLINK; how should my file be formatted?
+
+ A custom LD file should be tab-delimited. It should specify a reference variant ("SNP_A") and LD for all others relative to that variant ("SNP_B"). The first two rows will look like the following example, taken from actual PLINK output: +
+          CHR_A	BP_A	SNP_A	CHR_B	BP_B	SNP_B	R2
+ 22 37470224 22:37470224_T/C 22 37370297 22:37370297_T/C 0.000178517 +
+ +
Note: pre-calculated LD files can easily become very large. We recommend only outputting LD relative to a few target reference variants at a time. +
+

From b45b8018e4a32f530e3844fdaa8ad5d2812a94e0 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 6 Oct 2021 16:38:37 -0400 Subject: [PATCH 051/100] Add user-provided PLINK LD to the demonstration * Add UserTabixLD adapter to the extension, to help people use their custom LD with LocusZoom * Instructions for generating LD using PLINK-1.9 and reformatting for use with LZ (tested on 1000G phase 3 data) * Incorporate PLINK 1.9 custom LD into the tabix tracks demo with 3 refvars --- esm/components/data_layer/base.js | 2 +- esm/data/adapters.js | 2 +- esm/ext/lz-parsers/index.js | 62 +++++++++++++++++++++++++- examples/ext/tabix_tracks.html | 10 +++-- test/unit/components/test_datalayer.js | 4 +- 5 files changed, 71 insertions(+), 9 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 43b6ce11..abe45cd9 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -511,7 +511,7 @@ class BaseDataLayer { // Current implementation is a soft warning, so that certain "incremental enhancement" features // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info. // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data. - console.warn(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]} + console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]} Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules. `); diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 3d10843a..f373d1de 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -506,7 +506,7 @@ class LDServer extends BaseUMAdapter { } _performRequest(options) { - // Skip request if this one depends on other data, in a region with no data + // Skip request if this one depends on other data, and we are in a region with no data if (options._skip_request) { return Promise.resolve([]); } diff --git a/esm/ext/lz-parsers/index.js b/esm/ext/lz-parsers/index.js index fbb2e467..b67677ba 100644 --- a/esm/ext/lz-parsers/index.js +++ b/esm/ext/lz-parsers/index.js @@ -3,9 +3,67 @@ import { makeGWASParser } from './gwas/parsers'; import { guessGWAS } from './gwas/sniffers'; import { makePlinkLdParser } from './ld'; + +// Most of this plugin consists of standalone functions. But we can add a few simple custom classes to the registry that help to use parsed output +function install(LocusZoom) { + if (LocusZoom.Adapters.has('TabixUrlSource')) { + // Custom Tabix adapter depends on another extension being loaded first + const TabixUrlSource = LocusZoom.Adapters.get('TabixUrlSource'); + const LDServer = LocusZoom.Adapters.get('LDServer'); + + class UserTabixLD extends TabixUrlSource { + constructor(config) { + if (!config.limit_fields) { + config.limit_fields = ['variant2', 'position2', 'correlation']; + } + super(config); + } + + _buildRequestOptions(state, assoc_data) { + if (!assoc_data) { + throw new Error('LD request must depend on association data'); + } + // If no state refvar is provided, find the most significant variant in any provided assoc data. + // Assumes that assoc satisfies the "assoc" fields contract, eg has fields variant and log_pvalue + const base = super._buildRequestOptions(...arguments); + if (!assoc_data.length) { + base._skip_request = true; + return base; + } + + // NOTE: Reuses a method from another adapter to mix in functionality + base.ld_refvar = LDServer.prototype.__find_ld_refvar.bind(this)(state, assoc_data); + return base; + } + + _performRequest(options) { + // Skip request if this one depends on other data, and we are in a region with no data + if (options._skip_request) { + return Promise.resolve([]); + } + return super._performRequest(options); + } + + _annotateRecords(records, options) { + // A single PLINK LD file could contain several reference variants (SNP_A) in the same region. + // Only show LD relative to the user-selected refvar in this plot. + return records.filter((item) => item['variant1'] === options.ld_refvar); + } + } + + LocusZoom.Adapters.add('UserTabixLD', UserTabixLD); + } +} + +if (typeof LocusZoom !== 'undefined') { + // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use() + // eslint-disable-next-line no-undef + LocusZoom.use(install); +} + // Support UMD (single symbol export) -const all = { makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser }; +const all = { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser }; export default all; -export { makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser }; +export { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser }; diff --git a/examples/ext/tabix_tracks.html b/examples/ext/tabix_tracks.html index 99d4deb2..30ff0acb 100644 --- a/examples/ext/tabix_tracks.html +++ b/examples/ext/tabix_tracks.html @@ -10,10 +10,10 @@ - + - + LocusZoom.js ~ Tabix Tracks @@ -186,6 +186,7 @@

PLINK LD

stderr_beta_col: 8 }); const bedParser = LzParsers.makeBed12Parser({normalize: true}); + const ldParser = LzParsers.makePlinkLdParser({normalize: true}); const apiBase = "https://portaldev.sph.umich.edu/api/v1/"; const data_sources = new LocusZoom.DataSources() @@ -194,7 +195,10 @@

PLINK LD

parser_func: gwasParser, overfetch: 0, }]) - .add("ld", ["LDServer", { url: "https://portaldev.sph.umich.edu/ld/", source: '1000G', build: 'GRCh37', population: 'ALL' }]) + .add("ld", ["UserTabixLD", { + url_data: '../data/tabix-demo/plink.ld.tab.gz', + parser_func: ldParser, + }]) .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) .add("intervals", ["TabixUrlSource", { url_data: '../data/tabix-demo/DFF622JQK.bed.bgz', diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index 8d25f085..047221ae 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -65,10 +65,10 @@ describe('LocusZoom.DataLayer', function () { }); it('warns if the data received does not match the inferred fields contract', function () { - let spy = sinon.spy(console, 'warn'); + let spy = sinon.spy(console, 'debug'); this.layer.data = [{ 'assoc:variant': '1:23_A/B', 'assoc:position': 23 }]; this.layer.applyDataMethods(); - assert.ok(spy.calledOnce, 'Console.warn was called with data contract errors'); + assert.ok(spy.calledOnce, 'Console.debug was called with data contract errors'); assert.ok(spy.firstCall.args[0].match(/Missing fields are: assoc:rsid/), 'Developer message identifies the missing fields'); }); From 308272a11754c11896e12c119856e6e256210a7f Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 6 Oct 2021 17:21:50 -0400 Subject: [PATCH 052/100] Implement public S3 bucket for tabix demo assets --- examples/ext/tabix_tracks.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/ext/tabix_tracks.html b/examples/ext/tabix_tracks.html index 30ff0acb..a1417ffc 100644 --- a/examples/ext/tabix_tracks.html +++ b/examples/ext/tabix_tracks.html @@ -191,17 +191,21 @@

PLINK LD

const apiBase = "https://portaldev.sph.umich.edu/api/v1/"; const data_sources = new LocusZoom.DataSources() .add("assoc", ["TabixUrlSource", { - url_data: '../data/tabix-demo/gwas_giant-bmi_meta_women-only.gz', //'../data/tabix-demo/DFF622JQK.bed.bgz', + // Courtesy of https://www.ncbi.nlm.nih.gov/pubmed/25673413 - As harmonized in https://my.locuszoom.org/gwas/236887/ + url_data: 'https://locuszoom-web-demos.s3.us-east-2.amazonaws.com/tabix-demo/gwas_giant-bmi_meta_women-only.gz', parser_func: gwasParser, overfetch: 0, }]) .add("ld", ["UserTabixLD", { - url_data: '../data/tabix-demo/plink.ld.tab.gz', + // Fetch an LD file with just 3 possible refvars. More limited than an LD server, but able to use LD from a custom population. + url_data: 'https://locuszoom-web-demos.s3.us-east-2.amazonaws.com/tabix-demo/plink.ld.tab.gz', parser_func: ldParser, }]) + // Recombination rate is a standard dataset. Note that each dataset is fetched independently- we can mix built-in public APIs with tabix data when drawing the plot .add("recomb", ["RecombLZ", { url: apiBase + "annotation/recomb/results/", build: 'GRCh37' }]) .add("intervals", ["TabixUrlSource", { - url_data: '../data/tabix-demo/DFF622JQK.bed.bgz', + // This annotation track is courtesy of the CMDGA: https://cmdga.org/annotations/DSR953RQL/ + url_data: 'https://locuszoom-web-demos.s3.us-east-2.amazonaws.com/tabix-demo/DFF622JQK.bed.bgz', parser_func: bedParser, overfetch: 0.25, }]) From 146e9cb0bbd0ad7716593af1d4551159e76f4c70 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 6 Oct 2021 17:28:14 -0400 Subject: [PATCH 053/100] Fix UserTabixLD adapter bug when user panned far from the user-selected LD refvar --- esm/ext/lz-parsers/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esm/ext/lz-parsers/index.js b/esm/ext/lz-parsers/index.js index b67677ba..1df2023c 100644 --- a/esm/ext/lz-parsers/index.js +++ b/esm/ext/lz-parsers/index.js @@ -32,7 +32,7 @@ function install(LocusZoom) { } // NOTE: Reuses a method from another adapter to mix in functionality - base.ld_refvar = LDServer.prototype.__find_ld_refvar.bind(this)(state, assoc_data); + base.ld_refvar = LDServer.prototype.__find_ld_refvar(state, assoc_data); return base; } From f95001dd001244bd630a4ae1765c4812668d4e02 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 6 Oct 2021 18:01:16 -0400 Subject: [PATCH 054/100] Delete unused file --- esm/ext/lz-parsers.js | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 esm/ext/lz-parsers.js diff --git a/esm/ext/lz-parsers.js b/esm/ext/lz-parsers.js deleted file mode 100644 index e1d85b3d..00000000 --- a/esm/ext/lz-parsers.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Parsers for handling common file formats used by LocusZoom plots. Each parser is intended to be used on one line of - * text from the specified file. - * @module - */ - -import makeUcscBedParser from './lz-parsers/bed'; - -// // Slight build quirk: we use a single webpack config for all modules, but `libraryTarget` expects the entire -// // module to be exported as `default` in + * ``` + * + * To use with ES6 modules, import the helper functions and use them with your layout: + * + * ``` + * import { install, makeGWASParser, makeBed12Parser, makePlinkLDParser } from 'locuszoom/esm/ext/lz-parsers'; + * LocusZoom.use(install); + * ``` + * + * ### Features provided + * * {@link module:LocusZoom_Adapters~UserTabixLD} + * + * @module ext/lz-parsers + */ + import { makeBed12Parser } from './bed'; import { makeGWASParser } from './gwas/parsers'; import { guessGWAS } from './gwas/sniffers'; @@ -11,6 +34,12 @@ function install(LocusZoom) { const TabixUrlSource = LocusZoom.Adapters.get('TabixUrlSource'); const LDServer = LocusZoom.Adapters.get('LDServer'); + /** + * Load user-provided LD from a tabix file, and filter the returned set of records based on a reference variant. (will attempt to choose a reference variant based on the most significant association variant, if no state.ldrefvar is specified) + * @public + * @alias module:LocusZoom_Adapters~UserTabixLD + * @extends module:LocusZoom_Adapters~TabixUrlSource + */ class UserTabixLD extends TabixUrlSource { constructor(config) { if (!config.limit_fields) { @@ -51,6 +80,7 @@ function install(LocusZoom) { } } + LocusZoom.Adapters.add('UserTabixLD', UserTabixLD); } } diff --git a/esm/ext/lz-parsers/ld.js b/esm/ext/lz-parsers/ld.js index 0c0bd934..c0ebb214 100644 --- a/esm/ext/lz-parsers/ld.js +++ b/esm/ext/lz-parsers/ld.js @@ -9,8 +9,11 @@ import {normalizeMarker} from '../../helpers/parse'; * Parse the output of plink v1.9 R2 calculations relative to one (or a few) target SNPs. * See: https://www.cog-genomics.org/plink/1.9/ld and * reformatting commands at https://www.cog-genomics.org/plink/1.9/other - * - * @returns {Object} Same column names used by the UM LD Server + * @function + * @alias module:ext/lz-parsers~makePlinkLdParser + * @param {object} options + * @param {boolean} [options.normalize=true] Normalize fields to expected datatypes and format; if false, returns raw strings as given in the file + * @return {function} A configured parser function that runs on one line of text from an input file */ function makePlinkLdParser({normalize = true} = {}) { return (line) => { From e003577fec5828e779d7c8e6c9ec570d96ee19bf Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Sat, 30 Oct 2021 01:16:17 -0400 Subject: [PATCH 068/100] Add documentation for revamped adapter classes --- esm/data/adapters.js | 131 +++++++++++++++++++++++++--------- esm/ext/lz-intervals-track.js | 2 + 2 files changed, 100 insertions(+), 33 deletions(-) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index f373d1de..6e8575ff 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -41,6 +41,11 @@ import {parseMarker} from '../helpers/parse'; // methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the // private API methods exist in the base class. +/** + * Replaced with the BaseLZAdapter class. + * @public + * @deprecated + */ class BaseAdapter { constructor() { throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.'); @@ -48,15 +53,27 @@ class BaseAdapter { } /** - * Base class for LocusZoom data adapters that receive their data over the web. Adds default config parameters + * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters * (and potentially other behavior) that are relevant to URL-based requests. * @extends module:LocusZoom_Adapters~BaseAdapter + * @deprecated * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request. * @inheritDoc */ class BaseApiAdapter extends BaseAdapter {} +/** + * @param {object} config + * @param [config.cache_enabled=true] + * @param [config.cache_size=3] + * @param [config.url] + * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name. + * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant) + * Typically, this is only disabled if the response payload is very unusual + * @param {String[]} [limit_fields=null] If an API returns far more data than is needed, this can be used to simplify + * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD. + */ class BaseLZAdapter extends BaseUrlAdapter { constructor(config = {}) { super(config); @@ -71,6 +88,13 @@ class BaseLZAdapter extends BaseUrlAdapter { this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields } + /** + * Determine how a particular request will be identified in cache. Most LZ requests are region based, + * so the default is a string concatenation of `chr_start_end` + * @param options Receives plot.state plus any other request options defined by this source + * @returns {string} + * @private + */ _getCacheKey(options) { // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default let {chr, start, end} = options; // Current view: plot.state @@ -88,7 +112,10 @@ class BaseLZAdapter extends BaseUrlAdapter { } /** - * Note: since namespacing is the last thing we usually want to do, calculations will want to call super AFTER their own code. + * Add the "local namespace" as a prefix for every field returned for this request. Eg if the association api + * returns a field called variant, and the source is referred to as "assoc" within a particular data layer, then + * the returned records will have a field called "assoc:variant" + * * @param records * @param options * @returns {*} @@ -123,11 +150,12 @@ class BaseLZAdapter extends BaseUrlAdapter { * * In the last step of fetching data, LZ adds a prefix to each field name. * This means that operations like "build query based on prior data" can't just ask for "log_pvalue" because - * they are receiving "assoc.log_pvalue" or some such unknown prefix. + * they are receiving "assoc:log_pvalue" or some such unknown prefix. * - * This lets use easily use dependent data + * This helper lets us use dependent data more easily. Not every adapter needs to use this method. * - * @private + * @param {Object} a_record One record (often the first one in a set of records) + * @param {String} fieldname The desired fieldname, eg "log_pvalue" */ _findPrefixedKey(a_record, fieldname) { const suffixer = new RegExp(`:${fieldname}$`); @@ -140,11 +168,19 @@ class BaseLZAdapter extends BaseUrlAdapter { } +/** + * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies + * of one particular web server. + */ class BaseUMAdapter extends BaseLZAdapter { + /** + * @param {Object} config + * @param {String} [config.build] The genome build to be used by all requests for this adapter. + */ constructor(config = {}) { super(config); // The UM portaldev API accepts an (optional) parameter "genome_build" - this._genome_build = config.genome_build; + this._genome_build = config.genome_build || config.build; } _validateBuildSource(build, source) { @@ -159,6 +195,13 @@ class BaseUMAdapter extends BaseLZAdapter { } // Special behavior for the UM portaldev API: col -> row format normalization + /** + * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs. + * @param response_text + * @param options + * @returns {Object[]} + * @private + */ _normalizeResponse(response_text, options) { let data = super._normalizeResponse(...arguments); // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload @@ -195,6 +238,14 @@ class BaseUMAdapter extends BaseLZAdapter { } +/** + * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request + * to a specific REST API. + * @public + * @see module:LocusZoom_Adapters~BaseUMAdapter + * + * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL + */ class AssociationLZ extends BaseUMAdapter { constructor(config = {}) { super(config); @@ -226,6 +277,13 @@ class AssociationLZ extends BaseUMAdapter { * @see module:LocusZoom_Adapters~BaseUMAdapter */ class GwasCatalogLZ extends BaseUMAdapter { + /** + * @param {string} config.url The base URL for the remote data. + * @param [config.build] The genome build to use when requesting the specific genomic region. + * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. + * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and + * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37. + */ constructor(config) { if (!config.limit_fields) { config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant']; @@ -233,15 +291,6 @@ class GwasCatalogLZ extends BaseUMAdapter { super(config); } - /** - * @param {string} config.url The base URL for the remote data. - * @param {Object} config.params - * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant. - * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. - * @param {Number} [config.params.source] The ID of the chosen catalog. Most usages should omit this parameter and - * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37. - */ - /** * Add query parameters to the URL to construct a query for the specified region */ @@ -263,12 +312,11 @@ class GwasCatalogLZ extends BaseUMAdapter { /** * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format) * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter * @param {string} config.url The base URL for the remote data - * @param {Object} config.params - * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant. + * @param [config.build] The genome build to use * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. - * @param {Number} [config.params.source] The ID of the chosen gene dataset. Most usages should omit this parameter and + * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37. */ class GeneLZ extends BaseUMAdapter { @@ -306,13 +354,12 @@ class GeneLZ extends BaseUMAdapter { * matching on specific assumptions about `gene_name` format. * * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter */ class GeneConstraintLZ extends BaseLZAdapter { /** * @param {string} config.url The base URL for the remote data - * @param {Object} config.params - * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant. + * @param [config.build] The genome build to use * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. */ constructor(config = {}) { @@ -384,6 +431,23 @@ class GeneConstraintLZ extends BaseLZAdapter { } +/** + * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant. + * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS + * variant and yse that as the LD reference variant. + * + * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly + * if this information is not provided. + * + * This source is designed to connect its results to association data, and therefore depends on association data having + * been loaded by a previous request. For custom association APIs, some additional options might + * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt` + * are preferred, but this source will attempt to harmonize other common data formats into something that the LD + * server can understand. + * + * @public + * @see module:LocusZoom_Adapters~BaseUMAdapter + */ class LDServer extends BaseUMAdapter { constructor(config) { if (!config.limit_fields) { @@ -458,6 +522,8 @@ class LDServer extends BaseUMAdapter { // Assumes that assoc satisfies the "assoc" fields contract, eg has fields variant and log_pvalue const base = super._buildRequestOptions(...arguments); if (!assoc_data.length) { + // No variants, so no need to annotate association data with LD! + // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region) base._skip_request = true; return base; } @@ -508,6 +574,7 @@ class LDServer extends BaseUMAdapter { _performRequest(options) { // Skip request if this one depends on other data, and we are in a region with no data if (options._skip_request) { + // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird. return Promise.resolve([]); } @@ -540,12 +607,11 @@ class LDServer extends BaseUMAdapter { /** * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible) * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter * @param {string} config.url The base URL for the remote data - * @param {Object} config.params - * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant. + * @param [config.build] The genome build to use * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. - * @param {Number} [config.params.source] The ID of the chosen dataset. Most usages should omit this parameter and + * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37. */ class RecombLZ extends BaseUMAdapter { @@ -586,8 +652,8 @@ class RecombLZ extends BaseUMAdapter { * * Note: The name is a bit misleading. It receives JS objects, not strings serialized as "json". * @public - * @see module:LocusZoom_Adapters~BaseAdapter - * @param {object} data The data to be returned by this source (subject to namespacing rules) + * @see module:LocusZoom_Adapters~BaseLZAdapter + * @param {object} config.data The data to be returned by this source (subject to namespacing rules) */ class StaticSource extends BaseLZAdapter { constructor(config = {}) { @@ -595,7 +661,7 @@ class StaticSource extends BaseLZAdapter { super(...arguments); const { data } = config; if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key - throw new Error("'StaticSource' must provide data as required option 'data'"); + throw new Error("'StaticSource' must provide data as required option 'config.data'"); } this._data = data; } @@ -609,11 +675,10 @@ class StaticSource extends BaseLZAdapter { /** * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter * @param {string} config.url The base URL for the remote data - * @param {Object} config.params - * @param {String[]} config.params.build This datasource expects to be provided the name of the genome build that will - * be used to provide pheWAS results for this position. Note positions may not translate between builds. + * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will + * be used to provide PheWAS results for this position. Note positions may not translate between builds. */ class PheWASLZ extends BaseUMAdapter { _getURL(request_options) { diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index 33dbc5f7..e451bde6 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -8,8 +8,10 @@ * * {@link module:LocusZoom_ScaleFunctions~to_rgb} * * {@link module:LocusZoom_DataLayers~intervals} * * {@link module:LocusZoom_Layouts~standard_intervals} + * * {@link module:LocusZoom_Layouts~bed_intervals_layer} * * {@link module:LocusZoom_Layouts~intervals_layer} * * {@link module:LocusZoom_Layouts~intervals} + * * {@link module:LocusZoom_Layouts~bed_intervals} * * {@link module:LocusZoom_Layouts~interval_association} * * ### Loading and usage From 96d0f9c26638e42701cbabdb0dc85dc9b47b7947 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 1 Nov 2021 16:54:22 -0400 Subject: [PATCH 069/100] Improve docs for layer layouts, namespaces, and data ops. --- esm/components/data_layer/base.js | 23 +++++++++++++++++++++++ esm/ext/lz-intervals-track.js | 2 +- esm/ext/lz-parsers/bed.js | 3 ++- esm/ext/lz-parsers/index.js | 4 +++- esm/ext/lz-tabix-source.js | 2 +- esm/registry/data_ops.js | 17 ++++++++++++----- 6 files changed, 42 insertions(+), 9 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index fcf87fa7..686e8de3 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -52,6 +52,21 @@ import SCALABLE from '../../registry/scalable'; * @property value The target value to compare to */ +/** + * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting. + * @property {module:LocusZoom_DataFunctions|'fetch'} type + * @property {String[]} [from] For operations of type "fetch", this is required. By default, it will fill in any items provided in "namespace" (everything specified in namespace triggers an adapter/network request) + * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace). + * Eg, for ld to be fetched after association data, specify "ld(assoc)". Most LocusZoom examples fill in all items, in order to make the examples more clear. + * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation. + * Eg, if the retrieved data is combined via several left joins in series: `name: "assoc_plus_ld"` -> feeds into `{name: "final", requires: ["assoc_plus_ld"]}` + * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just + * the names of other namespaces (or data operations) whose results must be available before a join can be performed + * @property {String[]} params Any user-defined parameters that should be passed to the particular join function + * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']` + * Separate from this section, data functions will also receive a copy of "plot.state" automatically. + */ + /** * @typedef {object} LegendItem @@ -115,6 +130,14 @@ class BaseDataLayer { * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is * your job to assure that all of the expected fields are present in every element) + * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data ("assoc") + * to the globally unique name for something defined in LocusZoom.DataSources. + * Namespaces allow a single layout to be reused to plot many tracks of the same type: "given some form of association data, plot it". + * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse + * a layout with a new provider of data- like plotting two association studies stacked together- + * only the namespace section of the layout needs to be overridden. + * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})` + * @param {module:LocusZoom_DataLayers~DataOperation[]} data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions}) * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact * details vary from one layer to the next. See the Interactivity Tutorial for details. diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index e451bde6..c0806641 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -53,7 +53,7 @@ function install (LocusZoom) { * (**extension**) Retrieve Interval Annotation Data (e.g. BED Tracks), as fetched from the LocusZoom API server (or compatible) * @public * @alias module:LocusZoom_Adapters~IntervalLZ - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions * @param {number} config.params.source The numeric ID for a specific dataset as assigned by the API server */ diff --git a/esm/ext/lz-parsers/bed.js b/esm/ext/lz-parsers/bed.js index b0a4f64c..16f16adc 100644 --- a/esm/ext/lz-parsers/bed.js +++ b/esm/ext/lz-parsers/bed.js @@ -28,7 +28,8 @@ function _hasNum(value) { * * @function * @alias module:ext/lz-parsers~makeBed12Parser - * @param {Boolean} normalize Whether to normalize the output to the format expected by LocusZoom (eg type coercion + * @param {object} options + * @param {Boolean} options.normalize Whether to normalize the output to the format expected by LocusZoom (eg type coercion * for numbers, removing chr chromosome prefixes, and using 1-based and inclusive coordinates instead of 0-based disjoint intervals) * @return function A configured parser function that runs on one line of text from an input file */ diff --git a/esm/ext/lz-parsers/index.js b/esm/ext/lz-parsers/index.js index 564d48ce..6025f257 100644 --- a/esm/ext/lz-parsers/index.js +++ b/esm/ext/lz-parsers/index.js @@ -16,7 +16,7 @@ * ``` * * ### Features provided - * * {@link module:LocusZoom_Adapters~UserTabixLD} + * * {@link module:LocusZoom_Adapters~UserTabixLD} (if the {@link module:ext/lz-tabix-source} extension is loaded first) * * @module ext/lz-parsers */ @@ -39,6 +39,8 @@ function install(LocusZoom) { * @public * @alias module:LocusZoom_Adapters~UserTabixLD * @extends module:LocusZoom_Adapters~TabixUrlSource + * @see {@link module:ext/lz-tabix-source} for required extension and installation instructions + * @see {@link module:ext/lz-parsers} for required extension and installation instructions */ class UserTabixLD extends TabixUrlSource { constructor(config) { diff --git a/esm/ext/lz-tabix-source.js b/esm/ext/lz-tabix-source.js index 041d803d..61f955c7 100644 --- a/esm/ext/lz-tabix-source.js +++ b/esm/ext/lz-tabix-source.js @@ -51,7 +51,7 @@ function install(LocusZoom) { * * @alias module:LocusZoom_Adapters~TabixUrlSource * @see {@link module:ext/lz-tabix-source} for required extension and installation instructions - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseLZAdapter * @param {function} config.parser_func A function that parses a single line of text and returns (usually) a * structured object of data fields * @param {string} config.url_data The URL for the bgzipped and tabix-indexed file diff --git a/esm/registry/data_ops.js b/esm/registry/data_ops.js index f4789d08..e94a8bb5 100644 --- a/esm/registry/data_ops.js +++ b/esm/registry/data_ops.js @@ -1,15 +1,22 @@ /** * "Data operation" functions, with call signature (state, [recordsetA, recordsetB...], ...params) => combined_results * - * These usually operate on two recordsets (joins), but can operate on one recordset (eg grouping) or > 2 (hopefully rare) + * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation + * is a "join", such as combining association + LD together into a single set of records for plotting. Several join + * functions (that operate by analogy to SQL) are provided built-in. * - * Pieces of code designed to transform or connect well structured data, usually as returned from an API - * - * Most of these are `joins`: intended to combine two recordsets (like assoc + ld) to a single final source of truth + * Other use cases include grouping or filtering records; data operations can consider dynamic properties stored in plot.state. + * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data, + * this is the recommended path to do so) + * Usually, a data operation receives two recordsets (the left and right members of the join, like "assoc" and "ld"). + * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network + * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is + * uncommon. (if possible, try to provide your data with fewer adapters/network requests!) * * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some, * particularly for advanced features, may carry assumptions about field names/ formatting. - * (example: log_pvalue instead of pvalue, or variant specifier formats) + * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`, + * or it may match two datasets based on a specific way of identifying the variant) * * @module LocusZoom_DataFunctions */ From 5919eafd899b854c143c9aef9599a36011375c99 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 1 Nov 2021 17:29:47 -0400 Subject: [PATCH 070/100] Exclude private symbols from docs --- esm/components/data_layer/base.js | 2 +- esm/ext/lz-parsers/bed.js | 7 ++++++- esm/ext/lz-parsers/gwas/sniffers.js | 12 +++++++++++- esm/ext/lz-parsers/utils.js | 13 +++++++++++++ esm/helpers/parse.js | 5 +++++ 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 686e8de3..e63a3737 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -137,7 +137,7 @@ class BaseDataLayer { * a layout with a new provider of data- like plotting two association studies stacked together- * only the namespace section of the layout needs to be overridden. * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})` - * @param {module:LocusZoom_DataLayers~DataOperation[]} data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions}) + * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions}) * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact * details vary from one layer to the next. See the Interactivity Tutorial for details. diff --git a/esm/ext/lz-parsers/bed.js b/esm/ext/lz-parsers/bed.js index 16f16adc..bd6410bf 100644 --- a/esm/ext/lz-parsers/bed.js +++ b/esm/ext/lz-parsers/bed.js @@ -5,7 +5,9 @@ import {normalizeChr} from './utils'; - +/** + * @private + */ function _bedMissing(value) { // BED files specify . as the missing/ null value character if (value === null || value === undefined || value === '.') { @@ -14,6 +16,9 @@ function _bedMissing(value) { return value; } +/** + * @private + */ function _hasNum(value) { // Return a number, or null if value marked as missing value = _bedMissing(value); diff --git a/esm/ext/lz-parsers/gwas/sniffers.js b/esm/ext/lz-parsers/gwas/sniffers.js index 261a6b1a..c07da291 100644 --- a/esm/ext/lz-parsers/gwas/sniffers.js +++ b/esm/ext/lz-parsers/gwas/sniffers.js @@ -5,6 +5,9 @@ import { parseMarker } from '../../../helpers/parse'; import { MISSING_VALUES, parsePvalToLog, _missingToNull } from '../utils'; +/** + * @private + */ function isNumeric(val) { // Check whether an unparsed string is a numeric value" if (MISSING_VALUES.has(val)) { @@ -61,6 +64,7 @@ function levenshtein(a, b) { // https://github.com/trekhleb/javascript-algorithm /** * Return the index of the first column name that meets acceptance criteria + * @private * @param {String[]} column_synonyms * @param {String[]}header_names * @param {Number} threshold Tolerance for fuzzy matching (# edits) @@ -91,6 +95,7 @@ function findColumn(column_synonyms, header_names, threshold = 2) { * Return parser configuration for pvalues * * Returns 1-based column indices, for compatibility with parsers + * @private * @param header_row * @param data_rows * @returns {{}} @@ -130,7 +135,9 @@ function getPvalColumn(header_row, data_rows) { return null; } - +/** + * @private + */ function getChromPosRefAltColumns(header_row, data_rows) { // Returns 1-based column indices, for compatibility with parsers // Get from either a marker, or 4 separate columns @@ -177,6 +184,7 @@ function getChromPosRefAltColumns(header_row, data_rows) { /** * Identify which columns contain effect size (beta) and stderr of the effect size + * @private * @param {String[]} header_names * @param {Array[]} data_rows * @returns {{}} @@ -208,8 +216,10 @@ function getEffectSizeColumns(header_names, data_rows) { } return ret; } + /** * Attempt to guess the correct parser settings given a set of header rows and a set of data rows + * @private * @param {String[]} header_row * @param {String[][]} data_rows * @param {int} offset Used to convert between 0 and 1-based indexing. diff --git a/esm/ext/lz-parsers/utils.js b/esm/ext/lz-parsers/utils.js index 226c8e72..fb32ae7d 100644 --- a/esm/ext/lz-parsers/utils.js +++ b/esm/ext/lz-parsers/utils.js @@ -2,14 +2,21 @@ * Constant values used by GWAS parser */ +/** + * @private + */ const MISSING_VALUES = new Set(['', '.', 'NA', 'N/A', 'n/a', 'nan', '-nan', 'NaN', '-NaN', 'null', 'NULL', 'None', null]); +/** + * @private + */ const REGEX_PVAL = /([\d.-]+)([\sxeE]*)([0-9-]*)/; /** * Utility helper that checks for the presence of a numeric value (incl 0), * eg "has column specified" + * @private * @param num * @returns {boolean} */ @@ -20,6 +27,7 @@ function has(num) { /** * Convert all missing values to a standardized input form * Useful for columns like pvalue, where a missing value explicitly allowed + * @private */ function missingToNull(values, nulls = MISSING_VALUES, placeholder = null) { // TODO Make this operate on a single value; cache for efficiency? @@ -28,6 +36,7 @@ function missingToNull(values, nulls = MISSING_VALUES, placeholder = null) { /** * Normalize chromosome representations + * @private * @param {String} chromosome */ function normalizeChr(chromosome) { @@ -36,6 +45,7 @@ function normalizeChr(chromosome) { /** * Parse (and validate) a given number, and return the -log10 pvalue. + * @private * @param value * @param {boolean} is_neg_log * @returns {number|null} The -log10 pvalue @@ -80,6 +90,9 @@ function parsePvalToLog(value, is_neg_log = false) { return -Math.log10(val); } +/** + * @private + */ function parseAlleleFrequency({ freq, allele_count, n_samples, is_alt_effect = true }) { if (freq !== undefined && allele_count !== undefined) { throw new Error('Frequency and allele count options are mutually exclusive'); diff --git a/esm/helpers/parse.js b/esm/helpers/parse.js index 440095e9..b8dc25c9 100644 --- a/esm/helpers/parse.js +++ b/esm/helpers/parse.js @@ -2,10 +2,14 @@ * Parse useful entities */ +/** + * @private + */ const REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/; /** * Parse a single marker, cleaning up values as necessary + * @private * @param {String} value * @param {boolean} test If called in testing mode, do not throw an exception * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional) @@ -25,6 +29,7 @@ function parseMarker(value, test = false) { /** * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server * This allows harmonizing various input data to a consistent format + * @private * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc) */ function normalizeMarker(variant) { From 33dfbfbc2eb06606f5477a5018dc25403bdaeb44 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 1 Nov 2021 17:37:05 -0400 Subject: [PATCH 071/100] Update doc example to reflect actual usage --- assets/docs/rendering_layouts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/docs/rendering_layouts.md b/assets/docs/rendering_layouts.md index e6ef1924..eec9fa99 100644 --- a/assets/docs/rendering_layouts.md +++ b/assets/docs/rendering_layouts.md @@ -194,7 +194,7 @@ const data_layer = { // Name of a function specified in `LocusZoom.ScaleFunctions` scale_function: 'if', // The field whose value will be passed to the scale function - field: 'ld:isrefvar', + field: 'lz_is_ld_refvar', // Options that will be passed to the scale function; see documentation for available options parameters: { field_value: 1, @@ -203,7 +203,7 @@ const data_layer = { }, { scale_function: 'numerical_bin', - field: 'ld:state', + field: 'ld:correlation', parameters: { breaks: [0, 0.2, 0.4, 0.6, 0.8], values: ['#357ebd', '#46b8da', '#5cb85c', '#eea236', '#d43f3a'], From 4831bb0fee7f486a3a3e7bab22da1258f09161db Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 2 Nov 2021 15:38:16 -0400 Subject: [PATCH 072/100] Allow data operations to mutate the data_layer instance that initiates them. (typically by changing layout, panel, panel.layout, etc) In theory, could be used in future for things like "automatically create legend based on data". For now we document but don't promote this behavior heavily as the internals may evolve over time. The previous way to achieve self-defining datalayer layouts (subclassing an existing layer) was often annoying, esp since CSS properties were scoped to the name of the parent class, which often caused the child class to render incorrectly. Data operations provide a safety valve to modify a well defined thing in a specific way. --- assets/docs/data_retrieval.md | 8 ++++-- esm/components/data_layer/base.js | 2 +- esm/components/plot.js | 2 +- esm/data/requester.js | 33 ++++++++++++++++++------ esm/registry/data_ops.js | 19 ++++++++++---- test/unit/data/test_requester.js | 43 ++++++++++++++++++++++++++++--- 6 files changed, 87 insertions(+), 20 deletions(-) diff --git a/assets/docs/data_retrieval.md b/assets/docs/data_retrieval.md index 48ef286f..19ce6e4a 100644 --- a/assets/docs/data_retrieval.md +++ b/assets/docs/data_retrieval.md @@ -145,15 +145,19 @@ Some adapters can not begin to define their request until they see the data from Each individual adapter will receive data it depends on as a function argument to `getData`. The string syntax reflects how the data is connected internally! In the example above, "ld" is only retrieved after a request to "assoc" is complete, and the LD adapter received the assoc data to help define the next request. -> NOTE: Sometimes it is useful to extend an existing layout to fetch additional kinds of data. If an item is specified as a source of data for this layer (eg `namespace: {'assoc': 'assoc'}`), it will automatically be added to the `fetch.from` instruction. This means that everything listed in `data_layer.namespace` will trigger a data retrieval request. You only need to modify the list of names in `fetch.from` if you want to specify dependencies, eg "don't fetch LD until assoc data is ready". All of the built in LZ layouts are verbose and explicit, in an effort to reduce the amount of "magic" and make the examples easier to understand. +> NOTE: Sometimes it is useful to extend an existing layout to fetch additional kinds of data. It can be annoying to extend a "list" field like fetch.from, so LocusZoom has some magic to help out: if an item is specified as a source of data for this layer (eg `namespace: {'assoc': 'assoc'}`), it will automatically be added to the `fetch.from` instruction. This means that **everything listed in `data_layer.namespace` will trigger a data retrieval request**. You only need to modify the contents of `fetch.from` if you want to specify that the new item depends on something else, eg "don't fetch LD until assoc data is ready: `ld(assoc)`". All of the built in LZ layouts are verbose and explicit, in an effort to reduce the amount of "magic" and make the examples easier to understand. #### Joins ("Data functions") The second instruction in the example above is a *join*. It specifies how to compare multiple sets of data into one list of records ("one record per data point on the plot"). -Any function defined in `LocusZoom.DataFunctions` can be referenced. Several builtin joins are provided (including `left_match`, `inner_match`, and `full_outer_match`). Any arbitrary join function is possible, with the call signature `(state, [recordsetA, recordsetB...], ...params) => combined_results` +Any function defined in `LocusZoom.DataFunctions` can be referenced. Several builtin joins are provided (including `left_match`, `inner_match`, and `full_outer_match`). Any arbitrary join function can be added to the `LocusZoom.DataFunctions` registry, with the call signature `({plot_state, data_layer}}, [recordsetA, recordsetB...], ...params) => combined_results`. + +Data operations are synchronous; they are used for data formatting and cleanup. Use adapters for asynchronous operations like network requests. The plot will receive the last item in the dependency graph (taking into account both adapters and joins). If something looks strange, make sure that you have specified how to join all your data sources together. +> TIP: Because data functions are given access to plot.state and the data layer instance that initiated the request, they are able to do surprisingly powerful things, like filtering the returned data in response to a global plot_state parameter or auto-defining a layout (`data_layer.layout`) in response to dynamic data (axis labels, color scheme, etc). This is a very advanced usage and has the potential for side effects; use sparingly! See the LocusZoom unit tests for a few examples of what is possible. + # Creating your own custom adapter ## Can I reuse existing code? The built-in LocusZoom adapters can be used as-is in many cases, so long as the returned payload matches the expected format. You may need to write your own custom code (adapter subclass) in the following scenarios: diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index e63a3737..77534866 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -406,7 +406,7 @@ class BaseDataLayer { if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester const { namespace, data_operations } = this.layout; this._data_contract = findFields(this.layout, Object.keys(namespace)); - const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations); + const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this); this._entities = entities; this._dependencies = dependencies; } diff --git a/esm/components/plot.js b/esm/components/plot.js index 54b2736c..bfc7de02 100644 --- a/esm/components/plot.js +++ b/esm/components/plot.js @@ -706,7 +706,7 @@ class Plot { throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option"); } - const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); + const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot. const listener = () => { try { // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely, diff --git a/esm/data/requester.js b/esm/data/requester.js index 03b3a567..e746c8c5 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -1,11 +1,22 @@ +/** + * @module + * @private + */ import {getLinkedData} from 'undercomplicate'; import { DATA_OPS } from '../registry'; class DataOperation { - constructor(join_type, params) { + /** + * Perform a data operation (such as a join) + * @param {String} join_type + * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly! + * @param params Optional user/layout parameters to be passed to the data function + */ + constructor(join_type, initiator, params) { this._callable = DATA_OPS.get(join_type); + this._initiator = initiator; this._params = params || []; } @@ -14,8 +25,9 @@ class DataOperation { // Other ops are possible, like consolidating just one set of records to best value per key // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...] - // Every data operation receives plot_state, the input data, + any additional options - return Promise.resolve(this._callable(plot_state, dependent_recordsets, ...this._params)); + // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options + const context = {plot_state, data_layer: this._initiator}; + return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params)); } } @@ -51,9 +63,10 @@ class Requester { * removed adapter. * @param {Object} namespace_options * @param {Array} data_operations + * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations, but not adapters. * @returns {Array} Map of entities and list of dependencies */ - config_to_sources(namespace_options = {}, data_operations = []) { + config_to_sources(namespace_options = {}, data_operations = [], initiator) { const entities = new Map(); const namespace_local_names = Object.keys(namespace_options); @@ -112,7 +125,7 @@ class Requester { } }); - const task = new DataOperation(type, params); + const task = new DataOperation(type, initiator, params); entities.set(name, task); dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB) } @@ -122,19 +135,23 @@ class Requester { /** * - * @param {Object} state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end) + * @param {Object} context + * @param {Object} context.state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end) + * @param {Object|null} context.data_layer A reference to the data layer that initiated the request (if applicable). + * Data operations (but NOT adapters) are passed this property; it can be used to do things like auto-generate + * axis tick marks or panel legends after all dynamic data has been received. This is an advanced usage and should be handled with care! * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts. * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances * (things that implement a method getData). * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order * @returns {Promise} */ - getData(state, entities, dependencies) { + getData({ plot_state, data_layer }, entities, dependencies) { if (!dependencies.length) { return Promise.resolve([]); } // The last dependency (usually the last join operation) determines the last thing returned. - return getLinkedData(state, entities, dependencies, true); + return getLinkedData(plot_state, entities, dependencies, true); } } diff --git a/esm/registry/data_ops.js b/esm/registry/data_ops.js index e94a8bb5..630caf13 100644 --- a/esm/registry/data_ops.js +++ b/esm/registry/data_ops.js @@ -1,13 +1,19 @@ /** - * "Data operation" functions, with call signature (state, [recordsetA, recordsetB...], ...params) => combined_results + * "Data operation" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results * * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation * is a "join", such as combining association + LD together into a single set of records for plotting. Several join * functions (that operate by analogy to SQL) are provided built-in. * - * Other use cases include grouping or filtering records; data operations can consider dynamic properties stored in plot.state. + * Other use cases (even if no examples are in the built in code, see unit tests for what is possible): + * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state. * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data, * this is the recommended path to do so) + * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot), + * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg, + * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks + * (PheWAS), automatic panel legends or color schemes (BED tracks), etc. + * * Usually, a data operation receives two recordsets (the left and right members of the join, like "assoc" and "ld"). * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is @@ -32,9 +38,12 @@ import {RegistryBase} from './base'; const registry = new RegistryBase(); function _wrap_join(handle) { - // Validate number of arguments and convert call signature from (plot_state, deps, ...params) to (left, right, ...params). - // Must data operations are joins, so this wrapper is common shared code. - return (plot_state, deps, ...params) => { + // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params). + + // Many of our join functions are implemented with a different number of arguments than what a datafunction + // actually receives. (eg, a join function is generic and doesn't care about "context" information like plot.state) + // This wrapper is simple shared code to handle required validation and conversion stuff. + return (context, deps, ...params) => { if (deps.length !== 2) { throw new Error('Join functions must receive exactly two recordsets'); } diff --git a/test/unit/data/test_requester.js b/test/unit/data/test_requester.js index 0faddc11..07a610e8 100644 --- a/test/unit/data/test_requester.js +++ b/test/unit/data/test_requester.js @@ -7,7 +7,11 @@ import {DATA_OPS} from '../../../esm/registry'; describe('Requester object defines and parses requests', function () { describe('Layout parsing', function () { before(function () { - DATA_OPS.add('sumtwo', ({ state_field = 0 }, [left, right], some_param) => left + right + some_param + state_field); + DATA_OPS.add('sumtwo', ( + {plot_state: { state_field = 0 }}, + [left, right], + some_param + ) => left + right + some_param + state_field); }); after(function () { @@ -18,6 +22,7 @@ describe('Requester object defines and parses requests', function () { this._all_datasources = new Map([ ['assoc1', { name: 'assoc1', getData: () => Promise.resolve(1) }], ['someld', { name: 'someld', getData: () => Promise.resolve(2) }], + ['intervals', { name: 'intervals', getData: () => Promise.resolve(['a', 'b', 'c', 'd']) }], ['assoc2', { name: 'assoc2' }], ['catalog', { name: 'catalog' }], ]); @@ -103,7 +108,7 @@ describe('Requester object defines and parses requests', function () { const [entities, dependencies] = this._requester.config_to_sources(namespace_options, data_operations); - return this._requester.getData({}, entities, dependencies) + return this._requester.getData({ plot_state: {} }, entities, dependencies) .then((res) => { assert.equal(res, 6); // 1 + 2 + 3 }); @@ -123,12 +128,44 @@ describe('Requester object defines and parses requests', function () { const [entities, dependencies] = this._requester.config_to_sources(namespace_options, data_operations); - return this._requester.getData({ state_field: 20 }, entities, dependencies) + return this._requester.getData({ plot_state: { state_field: 20 } }, entities, dependencies) .then((res) => { assert.equal(res, 26); // 1 + 2 + 3 + 20 }); }); + it('passes a reference to the initiator, allowing it to do things like mutate the data layer layout when data is received', function () { + const namespace_options = {'intervals': 'intervals'}; + DATA_OPS.add('layer_mutator', ({plot_state, data_layer}, [data]) => { + // This sort of operation is very special-purpose and tied to the mock layout and changes we want to see! + data_layer.layout.mock_x_categories = data; + // An operation must always return data in the end. This usage is pretty esoteric, in that it exits solely to create side effects. + return data; + }); + + const data_operations = [ + { type: 'fetch', from: ['intervals'] }, + { + type: 'layer_mutator', + name: 'combined', + requires: ['intervals'], + params: [3], + }, + ]; + + const mock_layer = { + layout: { + mock_x_categories: [], + }, + }; + const [entities, dependencies] = this._requester.config_to_sources(namespace_options, data_operations, mock_layer); + + return this._requester.getData({ plot_state: { state_field: 20 } }, entities, dependencies) + .then((res) => { + assert.deepEqual(mock_layer.layout.mock_x_categories, res, 'In this example, mock categories exactly match returned data'); + }); + }); + it('tries to auto-generate data_operations[@type=fetch] if not provided', function () { const namespace_options = { assoc: 'assoc1', catalog: 'catalog', ld: 'someld' }; let data_operations = []; // no operations, fetch or otherwise From 68f28d1da434d4addbe9d66a8f5cbb5bddae6eb8 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 2 Nov 2021 20:46:32 -0400 Subject: [PATCH 073/100] Quick bugfix for prior change to getData signature config_to_sources receives a reference to data_layer; getData does not (because the reference to data_layer is baked into the join task) --- esm/data/requester.js | 13 +++++-------- test/unit/data/test_requester.js | 6 +++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/esm/data/requester.js b/esm/data/requester.js index e746c8c5..6a0b2b91 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -63,7 +63,9 @@ class Requester { * removed adapter. * @param {Object} namespace_options * @param {Array} data_operations - * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations, but not adapters. + * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations, + * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate + * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care! * @returns {Array} Map of entities and list of dependencies */ config_to_sources(namespace_options = {}, data_operations = [], initiator) { @@ -134,19 +136,14 @@ class Requester { } /** - * - * @param {Object} context - * @param {Object} context.state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end) - * @param {Object|null} context.data_layer A reference to the data layer that initiated the request (if applicable). - * Data operations (but NOT adapters) are passed this property; it can be used to do things like auto-generate - * axis tick marks or panel legends after all dynamic data has been received. This is an advanced usage and should be handled with care! + * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end) * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts. * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances * (things that implement a method getData). * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order * @returns {Promise} */ - getData({ plot_state, data_layer }, entities, dependencies) { + getData(plot_state, entities, dependencies) { if (!dependencies.length) { return Promise.resolve([]); } diff --git a/test/unit/data/test_requester.js b/test/unit/data/test_requester.js index 07a610e8..399f7e24 100644 --- a/test/unit/data/test_requester.js +++ b/test/unit/data/test_requester.js @@ -108,7 +108,7 @@ describe('Requester object defines and parses requests', function () { const [entities, dependencies] = this._requester.config_to_sources(namespace_options, data_operations); - return this._requester.getData({ plot_state: {} }, entities, dependencies) + return this._requester.getData({}, entities, dependencies) .then((res) => { assert.equal(res, 6); // 1 + 2 + 3 }); @@ -128,7 +128,7 @@ describe('Requester object defines and parses requests', function () { const [entities, dependencies] = this._requester.config_to_sources(namespace_options, data_operations); - return this._requester.getData({ plot_state: { state_field: 20 } }, entities, dependencies) + return this._requester.getData({ state_field: 20 }, entities, dependencies) .then((res) => { assert.equal(res, 26); // 1 + 2 + 3 + 20 }); @@ -160,7 +160,7 @@ describe('Requester object defines and parses requests', function () { }; const [entities, dependencies] = this._requester.config_to_sources(namespace_options, data_operations, mock_layer); - return this._requester.getData({ plot_state: { state_field: 20 } }, entities, dependencies) + return this._requester.getData({}, entities, dependencies) .then((res) => { assert.deepEqual(mock_layer.layout.mock_x_categories, res, 'In this example, mock categories exactly match returned data'); }); From ae00495d4a4c136dbadbbb331529d0833414c795 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 2 Nov 2021 21:48:27 -0400 Subject: [PATCH 074/100] Remove deprecated alias "dashboard.components" for "toolbar.widgets" and update doc examples (since layouts are receiving breaking changes, this is a good time to clean up) --- assets/docs/rendering_layouts.md | 14 +++++++------- esm/components/toolbar/index.js | 3 +-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/assets/docs/rendering_layouts.md b/assets/docs/rendering_layouts.md index eec9fa99..1c7a3acb 100644 --- a/assets/docs/rendering_layouts.md +++ b/assets/docs/rendering_layouts.md @@ -60,7 +60,7 @@ LocusZoom.Layouts.add('plot', 'standard_association', { responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, - dashboard: LocusZoom.Layouts.get('dashboard', 'standard_plot'), + toolbar: LocusZoom.Layouts.get('toolbar_widgets', 'standard_plot'), panels: [ LocusZoom.Layouts.get('panel', 'association', { height: 225 }), LocusZoom.Layouts.get('panel', 'genes', { height: 225 }) @@ -131,7 +131,7 @@ Since the registry just returns JSON-serializable objects, you could create a pl To see the list of pre-defined layouts (as well as any custom ones you have created): ```js > LocusZoom.Layouts.list(); -{plot: Array(4), panel: Array(7), data_layer: Array(9), dashboard: Array(4), dashboard_components: Array(1), tooltip: Array(5) } +{plot: Array(4), panel: Array(7), data_layer: Array(9), toolbar: Array(4), toolbar_widgets: Array(1), tooltip: Array(5) } ``` This will return a top level representation of the available types, showing the available categories. Note that even data layers are composed of smaller building blocks. This lets you use individual parts of a representation (like association tooltips) without committing to the other design choices for a common piece (like the association layer). @@ -162,18 +162,18 @@ Consider an association plot, where the only requested change is to use the righ The "modifications" object does not work as well for compound values, like a list, because this behavior is not well defined: changing the 5th element of a list could mean replacement, removal, or minor additions to fields... etc. In practice, this is quite often relevant... because panels and data layers are specified as lists. (order matters) -For complex scenarios like adding toolbar buttons or overriding the panels/data layers in a plot, you can build your own layout using all or some of the pieces of the layouts registry. One trick that is commonly used in the LocusZoom.js source code is to modify just one part of an existing array field via *self-calling functions* that immediately return a new, modified object. (this creates the modifications in-place, without leaving any temporary variables around afterwards) Eg, for a toolbar (dashboard) that adds just one extra button to an existing layout: +For complex scenarios like adding toolbar buttons or overriding the panels/data layers in a plot, you can build your own layout using all or some of the pieces of the layouts registry. One trick that is commonly used in the LocusZoom.js source code is to modify just one part of an existing array field via *self-calling functions* that immediately return a new, modified object. (this creates the modifications in-place, without leaving any temporary variables around afterwards) Eg, for a toolbar that adds just one extra button to an existing layout: ```js { - dashboard: (function () { - var base = LocusZoom.Layouts.get('dashboard', 'standard_panel', { unnamespaced: true }); - base.components.push({ + toolbar: (function () { + var base = LocusZoom.Layouts.get('toolbar', 'standard_panel'); + base.widgets.push({ type: 'toggle_legend', position: 'right' }); return base; - })() // Function calls itself immediately, so "dashboard" is set to the return value + })() // Function calls itself immediately, so "toolbar" is set to the return value } ``` diff --git a/esm/components/toolbar/index.js b/esm/components/toolbar/index.js index 8fb86383..097bb0d0 100644 --- a/esm/components/toolbar/index.js +++ b/esm/components/toolbar/index.js @@ -60,8 +60,7 @@ class Toolbar { */ initialize() { // Parse layout to generate widget instances - // In LZ 0.12, `dashboard.components` was renamed to `toolbar.widgets`. Preserve a backwards-compatible alias. - const options = (this.parent.layout.dashboard && this.parent.layout.dashboard.components) || this.parent.layout.toolbar.widgets; + const options = this.parent.layout.toolbar.widgets; if (Array.isArray(options)) { options.forEach((layout) => { try { From ce641a555f8c7dbebbaa4375bf146ce83c59646f Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 2 Nov 2021 22:49:33 -0400 Subject: [PATCH 075/100] Provide an interface method to add widgets after toolbar was created --- esm/components/toolbar/index.js | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/esm/components/toolbar/index.js b/esm/components/toolbar/index.js index 097bb0d0..f6b0c3e8 100644 --- a/esm/components/toolbar/index.js +++ b/esm/components/toolbar/index.js @@ -1,4 +1,4 @@ -import widgets from '../../registry/widgets'; +import WIDGETS from '../../registry/widgets'; import * as d3 from 'd3'; /** @@ -63,17 +63,11 @@ class Toolbar { const options = this.parent.layout.toolbar.widgets; if (Array.isArray(options)) { options.forEach((layout) => { - try { - const widget = widgets.create(layout.type, layout, this); - this.widgets.push(widget); - } catch (e) { - console.warn('Failed to create widget'); - console.error(e); - } + this.addWidget(layout); }); } - // Add mouseover event handlers to show/hide panel toolbar + // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown) if (this.type === 'panel') { d3.select(this.parent.parent.svg.node().parentNode) .on(`mouseover.${this.id}`, () => { @@ -92,6 +86,27 @@ class Toolbar { return this; } + /** + * Add a new widget to the toolbar. The widget may not show up correctly until the first time that + * @param {Object} layout The layout object describing the desired widget + * @param {boolean} render Whether to render the widget immediately. This option is useful when adding a toolbar widget + * dynamically, rather than creating a toolbar and rendering it all at once + * @returns {layout.type} + */ + addWidget(layout, render = false) { + try { + const widget = WIDGETS.create(layout.type, layout, this); + this.widgets.push(widget); + if (render) { + widget.show(); + } + return widget; + } catch (e) { + console.warn('Failed to create widget'); + console.error(e); + } + } + /** * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged * in an active drag event. From b9349b441049c67d2940174339b88b5c82803868 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 3 Nov 2021 15:48:19 -0400 Subject: [PATCH 076/100] Minor doc text improvements and note API wart in super-rarely used function --- esm/components/toolbar/index.js | 13 ++++++------- examples/ext/tabix_tracks.html | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/esm/components/toolbar/index.js b/esm/components/toolbar/index.js index f6b0c3e8..0354dcf0 100644 --- a/esm/components/toolbar/index.js +++ b/esm/components/toolbar/index.js @@ -87,19 +87,18 @@ class Toolbar { } /** - * Add a new widget to the toolbar. The widget may not show up correctly until the first time that + * Add a new widget to the toolbar. + * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to: + * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here. + * - call widget.show() to ensure that the widget is initialized and rendered correctly + * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default. * @param {Object} layout The layout object describing the desired widget - * @param {boolean} render Whether to render the widget immediately. This option is useful when adding a toolbar widget - * dynamically, rather than creating a toolbar and rendering it all at once * @returns {layout.type} */ - addWidget(layout, render = false) { + addWidget(layout) { try { const widget = WIDGETS.create(layout.type, layout, this); this.widgets.push(widget); - if (render) { - widget.show(); - } return widget; } catch (e) { console.warn('Failed to create widget'); diff --git a/examples/ext/tabix_tracks.html b/examples/ext/tabix_tracks.html index fce6ec21..5f7ef37f 100644 --- a/examples/ext/tabix_tracks.html +++ b/examples/ext/tabix_tracks.html @@ -90,7 +90,7 @@

BED files

- The following command is helpful for preparing BED files for use in the browser: + The following command is helpful for preparing BED files for use in the browser:
$ sort -k1,1 -k2,2n input.bed | bgzip > input-sorted.bed.gz && tabix -p bed input-sorted.bed.gz
Some BED files will have one or more header rows; in this case, modify the tabix command with: --skip-lines N (where N is the number of headers).

@@ -119,12 +119,12 @@ LocusZoom typically calculates LD relative to a variant by EPACTS-format specifier (chrom:pos_ref/alt). However, genotype VCF files have no single standard for how variants are identified, which can make it hard to match the requested variant to the actual data. Some files are not even internally consistent, which makes it hard to write easy copy-and-paste commands that would work widely - across files. Your file can be transformed to match the tutorial assumptions via common tool and the command below: + across files. Your file can be transformed to match the tutorial assumptions via common tool and the command below:
bcftools annotate -Oz --set-id '%CHROM\:%POS\_%REF\/%ALT' original_vcf.gz > vcfname_epacts_id_format.gz
In some rare cases (such as 1000G phase 3), data preparation errors may result in duplicate entries for the same variant. This can break PLINK. A command such as the one below can be used to find these duplicates:
- zcat < vcfname_epacts_id_format.gz | cut -f3 | sort | uniq -d
+ zcat < vcfname_epacts_id_format.gz | cut -f3 | sort | uniq -d
They can then be removed using the following command (check the output carefully before using, because reasons for duplicate lines vary widely):

bcftools norm -Oz --rm-dup all vcfname_epacts_id_format.gz > vcfname_epacts_id_format_rmdups.gz @@ -133,7 +133,7 @@
Calculating LD relative to reference variants
The command below will calculate LD relative to (several) variants in a 500 kb region centered around each reference variant.
- plink-1.9 --r2 --vcf vcfname_epacts_id_format.gz --ld-window 499999 --ld-window-kb 500 --ld-window-r2 0.0 --ld-snp-list mysnplist.txt + plink-1.9 --r2 --vcf vcfname_epacts_id_format.gz --ld-window 499999 --ld-window-kb 500 --ld-window-r2 0.0 --ld-snp-list mysnplist.txt
This command assumes the presence of a file named mysnplist.txt, which contains a series of rows like the example below:
16:53842908_G/A
@@ -145,7 +145,7 @@ 
         
As of this writing, this tutorial assumes a "list of SNPs" feature that requires PLINK 1.9.x (and is not yet available in newer versions). Unfortunately, PLINK's default output format is not compatible with tabix, for historical reasons. - Transform to a format readable by LocusZoom via the following sequence of commands. + Transform to a format readable by LocusZoom via the following sequence of commands.
cat plink.ld | tail -n+2 | sed 's/^[[:space:]]*//g' | sed 's/[[:space:]]*$//g' | tr -s ' ' '\t' | sort -k4,4 -k5,5n | bgzip > plink.ld.tab.gz && tabix -s4 -b5 -e5 plink.ld.tab.gz
From 2c23151ba931058b7040625f4fb785187c26366f Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 3 Nov 2021 20:57:52 -0400 Subject: [PATCH 077/100] Restore some docs that got lost during refactor --- esm/data/adapters.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 6e8575ff..cf0e68ec 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -449,6 +449,17 @@ class GeneConstraintLZ extends BaseLZAdapter { * @see module:LocusZoom_Adapters~BaseUMAdapter */ class LDServer extends BaseUMAdapter { + /** + * @param {string} config.url The base URL for the remote data. + * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant. + * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. + * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance. + * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display. + * @param [config.population='ALL'] The sample population used to calculate LD for a specified source; + * population names vary depending on the reference panel and how the server was populated wth data. + * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display. + * @param [config.method='rsquare'] The metric used to calculate LD + */ constructor(config) { if (!config.limit_fields) { config.limit_fields = ['variant2', 'position2', 'correlation']; From 3743d0b763495a2b9e00f7f3335b7d18e8b1264a Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 3 Nov 2021 21:30:32 -0400 Subject: [PATCH 078/100] Backwards compat helper for old code that specified options under ".params" in datasources.add --- esm/data/adapters.js | 6 ++++++ test/unit/data/test_adapters.js | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index cf0e68ec..214d40b8 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -76,6 +76,12 @@ class BaseApiAdapter extends BaseAdapter {} */ class BaseLZAdapter extends BaseUrlAdapter { constructor(config = {}) { + if (config.params) { + // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places. + console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'); + Object.assign(config, config.params || {}); + delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality! + } super(config); // Prefix the namespace for this source to all fieldnames: id -> assoc.id diff --git a/test/unit/data/test_adapters.js b/test/unit/data/test_adapters.js index f4b1444c..f1cfc869 100644 --- a/test/unit/data/test_adapters.js +++ b/test/unit/data/test_adapters.js @@ -39,6 +39,11 @@ describe('Data adapters', function () { } } + it('will merge options nested in config.params into the base options for backwards compat', function () { + const source = new BaseTestClass({ a: 1, params: {b: 2, c: 3, a: 4}}); + assert.deepEqual(source._config, {b: 2, c: 3, a: 4}, 'Options in config.params will be merged, and original .params child will be deleted'); + }); + it('checks if request can be satisfied using cached data', function () { const source = new BaseTestClass({ prefix_namespace: false }); const first_test_data = [{a: 1, b:2}]; From 64867281b58d24220402375e323d6ecddf5592da Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 9 Nov 2021 12:02:16 -0500 Subject: [PATCH 079/100] Add basic thumbnail for tabix tracks --- examples/ext/tabix_tracks.png | Bin 0 -> 11396 bytes index.html | 1 + 2 files changed, 1 insertion(+) create mode 100644 examples/ext/tabix_tracks.png diff --git a/examples/ext/tabix_tracks.png b/examples/ext/tabix_tracks.png new file mode 100644 index 0000000000000000000000000000000000000000..c11b22bd2902d6494b6fdc7ebc7e34d37b2f8af7 GIT binary patch literal 11396 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 Phenotypes (Layered) From 45c7ab2516298e4a6a0e177e63b6ac6545654086 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 10 Nov 2021 19:33:18 -0500 Subject: [PATCH 080/100] Draw association plots with an arrow indicating effect direction (depending on the values of beta and se, iff present) --- esm/layouts/index.js | 29 +++++++++++++++++++++-------- examples/ext/tabix_tracks.html | 6 +++++- test/unit/test_layouts.js | 2 +- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/esm/layouts/index.js b/esm/layouts/index.js index db0718d5..e4039478 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -161,15 +161,28 @@ const association_pvalues_layer = { coalesce: { active: true, }, - point_shape: { - scale_function: 'if', - field: 'lz_is_ld_refvar', - parameters: { - field_value: true, - then: 'diamond', - else: 'circle', + point_shape: [ + { + scale_function: 'if', + field: 'lz_is_ld_refvar', + parameters: { + field_value: true, + then: 'diamond', + }, }, - }, + { + // Not every dataset will provide these params + scale_function: 'effect_direction', + parameters: { + '+': 'triangle', + '-': 'triangledown', + // The scale function receives the entire datum object, so it needs to be told where to find individual fields + beta_field: 'assoc:beta', + stderr_beta_field: 'assoc:se', + }, + }, + 'circle', + ], point_size: { scale_function: 'if', field: 'lz_is_ld_refvar', diff --git a/examples/ext/tabix_tracks.html b/examples/ext/tabix_tracks.html index 5f7ef37f..2bff7c74 100644 --- a/examples/ext/tabix_tracks.html +++ b/examples/ext/tabix_tracks.html @@ -61,7 +61,11 @@
< return home

A key feature of LocusZoom is that each track is independent. This means it is very straightforward to define layouts in which some tracks come from a tabix file (like a BED track), while others are fetched from a remote - web server that handles standard well-known dataets (like genes or 1000G LD). + web server that handles standard well-known datasets (like genes or 1000G LD). +

+

+ In this demo, tabix loaders (and built-in file format parsers) are used for association, LD, and BED track data. + Genes and recombination rate are fetched from the UM PortalDev API. View the source code of this page for details.

diff --git a/test/unit/test_layouts.js b/test/unit/test_layouts.js index 28995391..b3e7c90d 100644 --- a/test/unit/test_layouts.js +++ b/test/unit/test_layouts.js @@ -337,7 +337,7 @@ describe('Layout helpers', function () { const scenarios = [ ['predicate_filters retrieve only list items where a specific condition is met', '$..color[?(@.scale_function === "if")].field', ['lz_is_ld_refvar']], - ['retrieves a list of scale function names', 'panels[?(@.tag === "association")]..scale_function', [ 'if', 'if', 'if', 'numerical_bin' ]], + ['retrieves a list of scale function names', 'panels[?(@.tag === "association")]..scale_function', [ 'if', 'numerical_bin', 'if', 'effect_direction', 'if' ]], ['fetches dotted field path - one specific axis label', 'panels[?(@.tag === "association")].axes.x.label', [ 'Chromosome {{chr}} (Mb)' ]], ['is able to query and return falsy values', '$.extra_field', [false]], ['returns an empty list if no matches are found', '$.nonexistent', []], From c1bc059bc4e48a743d11efaa8e36693dfe4f53e5 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 11 Nov 2021 11:00:14 -0500 Subject: [PATCH 081/100] Add doc notes on magical, "ignore extraneous" behavior of applyNamespaces --- esm/helpers/layouts.js | 12 +++++++++++- test/unit/test_layouts.js | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esm/helpers/layouts.js b/esm/helpers/layouts.js index eb61b5be..e9fc4b54 100644 --- a/esm/helpers/layouts.js +++ b/esm/helpers/layouts.js @@ -21,7 +21,17 @@ const triangledown = { }; /** - * Apply shared namespaces to a layout, recursively + * Apply shared namespaces to a layout, recursively. + * + * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout. + * For that, a key would have to be added to `layout.namespace` directly. + * + * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy + * over keys that are relevant to that data layer. Eg, if overrides specifies a key called "red_herring", + * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`. + * + * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify + * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself. * @private */ function applyNamespaces(layout, shared_namespaces) { diff --git a/test/unit/test_layouts.js b/test/unit/test_layouts.js index b3e7c90d..11f22901 100644 --- a/test/unit/test_layouts.js +++ b/test/unit/test_layouts.js @@ -218,6 +218,7 @@ describe('Layout helpers', function () { }; + // Note: a namespace is ONLY copied over if it is relevant to that data layer (other_anno should not appear anywhere) const actual = applyNamespaces(base, { assoc: 'assoc22', ld: 'ld5', other_anno: 'mything' }); assert.deepEqual(actual, expected, 'Overrides as appropriate'); }); From f96368110b88b952ab3de41a833fbbda09069e45 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 11 Nov 2021 12:56:46 -0500 Subject: [PATCH 082/100] Bump for preview release (0.14.0-beta.1) --- dist/ext/lz-aggregation-tests.min.js | 4 +- dist/ext/lz-aggregation-tests.min.js.map | 2 +- dist/ext/lz-credible-sets.min.js | 17 +- dist/ext/lz-credible-sets.min.js.map | 2 +- dist/ext/lz-dynamic-urls.min.js | 2 +- dist/ext/lz-forest-track.min.js | 4 +- dist/ext/lz-forest-track.min.js.map | 2 +- dist/ext/lz-intervals-enrichment.min.js | 4 +- dist/ext/lz-intervals-enrichment.min.js.map | 2 +- dist/ext/lz-intervals-track.min.js | 4 +- dist/ext/lz-intervals-track.min.js.map | 2 +- dist/ext/lz-parsers.min.js | 3 + dist/ext/lz-parsers.min.js.map | 1 + dist/ext/lz-tabix-source.min.js | 4 +- dist/ext/lz-tabix-source.min.js.map | 2 +- dist/ext/lz-widget-addons.min.js | 4 +- dist/ext/lz-widget-addons.min.js.map | 2 +- dist/locuszoom.app.min.js | 4 +- dist/locuszoom.app.min.js.map | 2 +- docs/api/LayoutRegistry.html | 14 +- docs/api/Line.html | 8 +- docs/api/Panel.html | 76 +- docs/api/Plot.html | 261 ++- docs/api/TransformationFunctionsRegistry.html | 2 +- ...onents_data_layer_annotation_track.js.html | 2 +- docs/api/components_data_layer_arcs.js.html | 2 +- docs/api/components_data_layer_base.js.html | 271 ++- docs/api/components_data_layer_genes.js.html | 2 +- ...nents_data_layer_highlight_regions.js.html | 4 +- docs/api/components_data_layer_line.js.html | 15 +- .../api/components_data_layer_scatter.js.html | 56 +- docs/api/components_legend.js.html | 4 +- docs/api/components_panel.js.html | 289 ++- docs/api/components_plot.js.html | 325 ++- docs/api/components_toolbar_index.js.html | 39 +- docs/api/components_toolbar_widgets.js.html | 4 +- docs/api/data_adapters.js.html | 1428 ++++-------- docs/api/data_field.js.html | 41 +- docs/api/data_requester.js.html | 181 +- docs/api/data_sources.js.html | 2 +- docs/api/ext_lz-credible-sets.js.html | 160 +- docs/api/ext_lz-dynamic-urls.js.html | 2 +- docs/api/ext_lz-forest-track.js.html | 26 +- docs/api/ext_lz-intervals-enrichment.js.html | 52 +- docs/api/ext_lz-intervals-track.js.html | 117 +- docs/api/ext_lz-parsers_bed.js.html | 165 ++ docs/api/ext_lz-parsers_gwas_parsers.js.html | 228 ++ docs/api/ext_lz-parsers_index.js.html | 152 ++ docs/api/ext_lz-parsers_ld.js.html | 88 + docs/api/ext_lz-tabix-source.js.html | 61 +- docs/api/ext_lz-widget-addons.js.html | 12 +- docs/api/global.html | 270 ++- docs/api/helpers_common.js.html | 2 +- docs/api/helpers_display.js.html | 4 +- docs/api/helpers_jsonpath.js.html | 2 +- docs/api/helpers_layouts.js.html | 120 +- docs/api/helpers_render.js.html | 2 +- docs/api/helpers_scalable.js.html | 81 +- docs/api/helpers_transforms.js.html | 2 +- docs/api/index.html | 24 +- docs/api/index.js.html | 6 +- docs/api/layouts_index.js.html | 217 +- docs/api/module-LocusZoom-DataSources.html | 2 +- docs/api/module-LocusZoom.html | 80 +- ...dule-LocusZoom_Adapters-AssociationLZ.html | 378 +--- ...module-LocusZoom_Adapters-BaseAdapter.html | 1955 +---------------- ...ule-LocusZoom_Adapters-BaseApiAdapter.html | 1952 +--------------- ...dule-LocusZoom_Adapters-BaseLZAdapter.html | 606 +++++ ...dule-LocusZoom_Adapters-BaseUMAdapter.html | 282 +++ ...le-LocusZoom_Adapters-ConnectorSource.html | 283 --- ...dule-LocusZoom_Adapters-CredibleSetLZ.html | 39 +- ...e-LocusZoom_Adapters-GeneConstraintLZ.html | 251 +-- .../api/module-LocusZoom_Adapters-GeneLZ.html | 317 +-- ...dule-LocusZoom_Adapters-GwasCatalogLZ.html | 162 +- .../module-LocusZoom_Adapters-IntervalLZ.html | 6 +- .../module-LocusZoom_Adapters-LDServer.html | 844 +------ .../module-LocusZoom_Adapters-PheWASLZ.html | 59 +- .../module-LocusZoom_Adapters-RecombLZ.html | 73 +- ...odule-LocusZoom_Adapters-StaticSource.html | 12 +- ...ule-LocusZoom_Adapters-TabixUrlSource.html | 47 +- ...module-LocusZoom_Adapters-UserTabixLD.html | 201 ++ docs/api/module-LocusZoom_Adapters.html | 14 +- docs/api/module-LocusZoom_DataFunctions.html | 1132 ++++++++++ ...le-LocusZoom_DataLayers-BaseDataLayer.html | 212 +- ...LocusZoom_DataLayers-annotation_track.html | 2 +- .../api/module-LocusZoom_DataLayers-arcs.html | 2 +- ...-LocusZoom_DataLayers-category_forest.html | 4 +- ...LocusZoom_DataLayers-category_scatter.html | 2 +- .../module-LocusZoom_DataLayers-forest.html | 2 +- .../module-LocusZoom_DataLayers-genes.html | 2 +- ...ocusZoom_DataLayers-highlight_regions.html | 2 +- ...module-LocusZoom_DataLayers-intervals.html | 8 +- ...sZoom_DataLayers-intervals_enrichment.html | 2 +- ...-LocusZoom_DataLayers-orthogonal_line.html | 76 +- .../module-LocusZoom_DataLayers-scatter.html | 2 +- docs/api/module-LocusZoom_DataLayers.html | 265 ++- docs/api/module-LocusZoom_Layouts.html | 243 +- docs/api/module-LocusZoom_MatchFunctions.html | 2 +- docs/api/module-LocusZoom_ScaleFunctions.html | 290 ++- ...ule-LocusZoom_TransformationFunctions.html | 2 +- .../module-LocusZoom_Widgets-BaseWidget.html | 2 +- .../api/module-LocusZoom_Widgets-_Button.html | 2 +- ...ule-LocusZoom_Widgets-display_options.html | 2 +- ...module-LocusZoom_Widgets-download_png.html | 2 +- ...module-LocusZoom_Widgets-download_svg.html | 2 +- ...module-LocusZoom_Widgets-filter_field.html | 2 +- docs/api/module-LocusZoom_Widgets-menu.html | 2 +- ...ule-LocusZoom_Widgets-move_panel_down.html | 2 +- ...odule-LocusZoom_Widgets-move_panel_up.html | 2 +- ...module-LocusZoom_Widgets-region_scale.html | 2 +- ...module-LocusZoom_Widgets-remove_panel.html | 2 +- ...dule-LocusZoom_Widgets-resize_to_data.html | 2 +- .../module-LocusZoom_Widgets-set_state.html | 2 +- ...module-LocusZoom_Widgets-shift_region.html | 2 +- docs/api/module-LocusZoom_Widgets-title.html | 2 +- ...odule-LocusZoom_Widgets-toggle_legend.html | 2 +- ...LocusZoom_Widgets-toggle_split_tracks.html | 4 +- .../module-LocusZoom_Widgets-zoom_region.html | 2 +- docs/api/module-LocusZoom_Widgets.html | 2 +- docs/api/module-components_legend-Legend.html | 2 +- .../module-data_requester-DataOperation.html | 255 +++ docs/api/module-ext_lz-credible-sets.html | 3 +- docs/api/module-ext_lz-dynamic-urls.html | 2 +- docs/api/module-ext_lz-forest-track.html | 2 +- .../module-ext_lz-intervals-enrichment.html | 2 +- docs/api/module-ext_lz-intervals-track.html | 4 +- docs/api/module-ext_lz-parsers.html | 1280 +++++++++++ docs/api/module-ext_lz-tabix-source.html | 5 +- ...ext_lz-widget-addons-covariates_model.html | 2 +- ...dule-ext_lz-widget-addons-data_layers.html | 2 +- docs/api/module-ext_lz-widget-addons.html | 2 +- .../module-registry_base-RegistryBase.html | 2 +- docs/api/registry_adapters.js.html | 2 +- docs/api/registry_base.js.html | 2 +- docs/api/registry_data_layers.js.html | 2 +- docs/api/registry_data_ops.js.html | 224 ++ docs/api/registry_layouts.js.html | 42 +- docs/api/registry_matchers.js.html | 2 +- docs/api/registry_transforms.js.html | 2 +- docs/api/registry_widgets.js.html | 2 +- docs/guides/data_retrieval.html | 177 +- docs/guides/interactivity.html | 342 ++- docs/guides/rendering_layouts.html | 281 +-- esm/version.js | 2 +- index.html | 4 +- package-lock.json | 2 +- package.json | 2 +- 147 files changed, 8535 insertions(+), 8855 deletions(-) create mode 100644 dist/ext/lz-parsers.min.js create mode 100644 dist/ext/lz-parsers.min.js.map create mode 100644 docs/api/ext_lz-parsers_bed.js.html create mode 100644 docs/api/ext_lz-parsers_gwas_parsers.js.html create mode 100644 docs/api/ext_lz-parsers_index.js.html create mode 100644 docs/api/ext_lz-parsers_ld.js.html create mode 100644 docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html create mode 100644 docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html delete mode 100644 docs/api/module-LocusZoom_Adapters-ConnectorSource.html create mode 100644 docs/api/module-LocusZoom_Adapters-UserTabixLD.html create mode 100644 docs/api/module-LocusZoom_DataFunctions.html create mode 100644 docs/api/module-data_requester-DataOperation.html create mode 100644 docs/api/module-ext_lz-parsers.html create mode 100644 docs/api/registry_data_ops.js.html diff --git a/dist/ext/lz-aggregation-tests.min.js b/dist/ext/lz-aggregation-tests.min.js index 7b4c436c..efdf7096 100644 --- a/dist/ext/lz-aggregation-tests.min.js +++ b/dist/ext/lz-aggregation-tests.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.4 */ -var LzAggregationTests;(()=>{"use strict";var e={d:(t,r)=>{for(var a in r)e.o(r,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:r[a]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>n});const r=raremetal;function a(e){const t=e.Adapters.get("BaseAdapter"),a=e.Adapters.get("BaseApiAdapter"),n=e.Adapters.get("ConnectorSource");e.Adapters.add("AggregationTestSourceLZ",class extends a{getURL(e,t,r){const a=e.aggregation_tests||{};t.header||(t.header={}),t.header.aggregation_genoset_id=a.genoset_id||null,t.header.aggregation_genoset_build=a.genoset_build||null,t.header.aggregation_phenoset_id=a.phenoset_id||null,t.header.aggregation_pheno=a.pheno||null,t.header.aggregation_calcs=a.calcs||{};const n=a.masks||[];return t.header.aggregation_masks=n,t.header.aggregation_mask_ids=n.map((function(e){return e.name})),this.url}getCacheKey(e,t,r){return this.getURL(e,t,r),JSON.stringify({chrom:e.chr,start:e.start,stop:e.end,genotypeDataset:t.header.aggregation_genoset_id,phenotypeDataset:t.header.aggregation_phenoset_id,phenotype:t.header.aggregation_pheno,samples:"ALL",genomeBuild:t.header.aggregation_genoset_build,masks:t.header.aggregation_mask_ids})}fetchRequest(e,t,r){const a=this.getURL(e,t,r),n=this.getCacheKey(e,t,r);return fetch(a,{method:"POST",body:n,headers:{"Content-Type":"application/json"}}).then((e=>{if(!e.ok)throw new Error(e.statusText);return e.text()})).then((function(e){const t="string"==typeof e?JSON.parse(e):e;if(t.error)throw new Error(t.error);return t}))}annotateData(e,t){if(!e.groups)return{groups:[],variants:[]};e.groups=e.groups.filter((function(e){return"GENE"===e.groupType}));const a=r.helpers.parsePortalJSON(e);let n=a[0];const o=a[1];n=n.byMask(t.header.aggregation_mask_ids);const s=t.header.aggregation_calcs;if(!s||0===Object.keys(s).length)return{variants:[],groups:[],results:[]};return new r.helpers.PortalTestRunner(n,o,s).toJSON().then((function(e){const r=t.header.aggregation_masks.reduce((function(e,t){return e[t.name]=t.description,e}),{});return e.data.groups.forEach((function(e){e.mask_name=r[e.mask]})),e.data})).catch((function(e){throw console.error(e),new Error("Failed to calculate aggregation test results")}))}normalizeResponse(e){return e}combineChainBody(e,t){return t.body}}),e.Adapters.add("AssocFromAggregationLZ",class extends t{constructor(e){if(!e||!e.from)throw"Must specify the name of the source that contains association data";super(...arguments)}parseInit(e){super.parseInit(e),this._from=e.from}getRequest(e,t,r){if(t.discrete&&!t.discrete[this._from])throw`${this.constructor.SOURCE_NAME} cannot be used before loading required data for: ${this._from}`;return Promise.resolve(JSON.parse(JSON.stringify(t.discrete[this._from].variants)))}normalizeResponse(e){const t=new RegExp("(?:chr)?(.+):(\\d+)_?(\\w+)?/?([^_]+)?_?(.*)?");return e.map((e=>{const r=e.variant.match(t);return{variant:e.variant,chromosome:r[1],position:+r[2],ref_allele:r[3],ref_allele_freq:1-e.altFreq,log_pvalue:-Math.log10(e.pvalue)}})).sort(((e,t)=>(e=e.variant)<(t=t.variant)?-1:e>t?1:0))}}),e.Adapters.add("GeneAggregationConnectorLZ",class extends n{_getRequiredSources(){return["gene_ns","aggregation_ns"]}combineChainBody(e,t){const r=this._source_name_mapping.aggregation_ns,a=this._source_name_mapping.gene_ns,n=t.discrete[r],o=t.discrete[a],s={};return n.groups.forEach((function(e){Object.prototype.hasOwnProperty.call(s,e.group)||(s[e.group]=[]),s[e.group].push(e.pvalue)})),o.forEach((function(e){const t=e.gene_name,r=s[t];r&&(e.aggregation_best_pvalue=Math.min.apply(null,r))})),o}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(a);const n=a;LzAggregationTests=t.default})(); +/*! Locuszoom 0.14.0-beta.1 */ +var LzAggregationTests;(()=>{"use strict";var e={d:(t,r)=>{for(var s in r)e.o(r,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:r[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>o});const r=raremetal;function s(e){const t=e.Adapters.get("BaseLZAdapter");e.DataFunctions.add("gene_plus_aggregation",((e,[t,r])=>{const s={};return r.groups.forEach((function(e){Object.prototype.hasOwnProperty.call(s,e.group)||(s[e.group]=[]),s[e.group].push(e.pvalue)})),t.forEach((e=>{const t=e.gene_name,r=s[t];r&&(e.aggregation_best_pvalue=Math.min.apply(null,r))})),t})),e.Adapters.add("AggregationTestSourceLZ",class extends t{constructor(e){e.prefix_namespace=!1,super(e)}_buildRequestOptions(e){const{aggregation_tests:t={}}=e,{genoset_id:r=null,genoset_build:s=null,phenoset_build:o=null,pheno:n=null,calcs:a={},masks:u=[]}=t;return t.mask_ids=u.map((e=>e.name)),e.aggregation_tests=t,e}_getURL(e){return this._url}_getCacheKey(e){const{chr:t,start:r,end:s,aggregation_tests:o}=e,{genoset_id:n=null,genoset_build:a=null,phenoset_id:u=null,pheno:l=null,mask_ids:g}=o;return JSON.stringify({chrom:t,start:r,stop:s,genotypeDataset:n,phenotypeDataset:u,phenotype:l,samples:"ALL",genomeBuild:a,masks:g})}_performRequest(e){const t=this._getURL(e),r=this._getCacheKey(e);return fetch(t,{method:"POST",body:r,headers:{"Content-Type":"application/json"}}).then((e=>{if(!e.ok)throw new Error(e.statusText);return e.text()})).then((e=>{const t="string"==typeof e?JSON.parse(e):e;if(t.error)throw new Error(t.error);return t.data}))}_annotateRecords(e,t){const{aggregation_tests:s}=t,{calcs:o=[],mask_ids:n=[],masks:a=[]}=s;if(!e.groups)return{groups:[],variants:[]};e.groups=e.groups.filter((e=>"GENE"===e.groupType));const u=r.helpers.parsePortalJSON(e);let l=u[0];const g=u[1];if(l=l.byMask(n),!o||0===Object.keys(o).length)return{variants:[],groups:[],results:[]};return new r.helpers.PortalTestRunner(l,g,o).toJSON().then((function(e){const t=a.reduce(((e,t)=>(e[t.name]=t.description,e)),{});return e.data.groups.forEach((e=>{e.mask_name=t[e.mask]})),e.data})).catch((function(e){throw console.error(e),new Error("Failed to calculate aggregation test results")}))}}),e.Adapters.add("AssocFromAggregationLZ",class extends t{_buildRequestOptions(e,t){if(!t)throw new Error("Aggregation test results must be provided");return e._agg_results=t,e}_performRequest(e){return Promise.resolve(e._agg_results.variants)}_normalizeResponse(e){const t=new RegExp("(?:chr)?(.+):(\\d+)_?(\\w+)?/?([^_]+)?_?(.*)?");return e.map((e=>{const{variant:r,altFreq:s,pvalue:o}=e,n=r.match(t),[a,u,l,g]=n;return{variant:r,chromosome:u,position:+l,ref_allele:g,ref_allele_freq:1-s,log_pvalue:-Math.log10(o)}})).sort(((e,t)=>(e=e.variant)<(t=t.variant)?-1:e>t?1:0))}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(s);const o=s;LzAggregationTests=t.default})(); //# sourceMappingURL=lz-aggregation-tests.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-aggregation-tests.min.js.map b/dist/ext/lz-aggregation-tests.min.js.map index f5a545ea..c8b8024a 100644 --- a/dist/ext/lz-aggregation-tests.min.js.map +++ b/dist/ext/lz-aggregation-tests.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"raremetal\"","webpack://[name]/./esm/ext/lz-aggregation-tests.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","raremetal","install","LocusZoom","BaseAdapter","Adapters","BaseApiAdapter","ConnectorSource","add","state","chain","fields","required_info","aggregation_tests","header","aggregation_genoset_id","genoset_id","aggregation_genoset_build","genoset_build","aggregation_phenoset_id","phenoset_id","aggregation_pheno","pheno","aggregation_calcs","calcs","mask_data","masks","aggregation_masks","aggregation_mask_ids","map","item","name","this","url","getURL","JSON","stringify","chrom","chr","start","stop","end","genotypeDataset","phenotypeDataset","phenotype","samples","genomeBuild","body","getCacheKey","fetch","method","headers","then","response","ok","Error","statusText","text","resp","json","parse","error","records","groups","variants","filter","groupType","parsed","helpers","byMask","keys","length","results","toJSON","res","mask_id_to_desc","reduce","acc","val","description","data","forEach","group","mask_name","mask","catch","e","console","config","from","super","arguments","parseInit","_from","discrete","constructor","SOURCE_NAME","Promise","resolve","REGEX_EPACTS","RegExp","one_variant","match","variant","chromosome","position","ref_allele","ref_allele_freq","altFreq","log_pvalue","Math","log10","pvalue","sort","a","b","aggregation_source_id","_source_name_mapping","gene_source_id","aggregationData","genesData","groupedAggregation","result","push","gene","gene_id","gene_name","tests","aggregation_best_pvalue","min","apply","use"],"mappings":"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","file":"ext/lz-aggregation-tests.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = raremetal;","/**\n * LocusZoom extensions used to calculate and render aggregation test results. Because these calculations depend on an\n * external library, the special data adapters are defined here, rather than in LocusZoom core code.\n *\n * This extension provides a number of features that are closely tied to the aggregation tests demo,\n * and thus the specific UI and data operations are less of a drop-in generic addon than most other extensions.\n * This tool also depends on a calculation tool (like RAREMETAL-server) with access to sample specific genotypes.\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n * - raremetal.js (available via NPM or a related CDN)\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import aggTests from 'locuszoom/esm/ext/lz-aggregation-tests';\n * LocusZoom.use(aggTests);\n * ```\n *\n * Then use the layouts and data adapters made available by this extension. (see demos and documentation for guidance)\n * @private\n * @module\n */\n// This is defined as a UMD module, to work with multiple different module systems / bundlers\n// Arcane build note: everything defined here gets registered globally. This is not a \"pure\" module, and some build\n// systems may require being told that this file has side effects.\n\nimport {helpers} from 'raremetal.js';\n\nfunction install (LocusZoom) {\n const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter');\n const BaseApiAdapter = LocusZoom.Adapters.get('BaseApiAdapter');\n const ConnectorSource = LocusZoom.Adapters.get('ConnectorSource');\n\n /**\n * Calculates gene or region-based tests based on provided data, using the raremetal.js library.\n * It will rarely be used by itself, but rather using a connector that attaches the results to data from\n * another source (like genes). Using a separate connector allows us to add caching and run this front-end\n * calculation only once, while using it in many different places.\n * @see module:ext/lz-aggregation-tests\n * @private\n */\n class AggregationTestSourceLZ extends BaseApiAdapter {\n getURL(state, chain, fields) {\n // Unlike most sources, calculations may require access to plot state data even after the initial request\n // This example source REQUIRES that the external UI widget would store the needed test definitions in a plot state\n // field called `aggregation_tests` (an object {masks: [], calcs: {})\n const required_info = state.aggregation_tests || {};\n\n if (!chain.header) {\n chain.header = {};\n }\n // All of these fields are required in order to use this datasource. TODO: Add validation?\n chain.header.aggregation_genoset_id = required_info.genoset_id || null; // Number\n chain.header.aggregation_genoset_build = required_info.genoset_build || null; // String\n chain.header.aggregation_phenoset_id = required_info.phenoset_id || null; // Number\n chain.header.aggregation_pheno = required_info.pheno || null; // String\n chain.header.aggregation_calcs = required_info.calcs || {}; // String[]\n const mask_data = required_info.masks || [];\n chain.header.aggregation_masks = mask_data; // {name:desc}[]\n chain.header.aggregation_mask_ids = mask_data.map(function (item) {\n return item.name;\n }); // Number[]\n return this.url;\n }\n\n getCacheKey(state, chain, fields) {\n this.getURL(state, chain, fields); // TODO: This just sets the chain.header fields\n return JSON.stringify({\n chrom: state.chr,\n start: state.start,\n stop: state.end,\n genotypeDataset: chain.header.aggregation_genoset_id,\n phenotypeDataset: chain.header.aggregation_phenoset_id,\n phenotype: chain.header.aggregation_pheno,\n samples: 'ALL',\n genomeBuild: chain.header.aggregation_genoset_build,\n masks: chain.header.aggregation_mask_ids,\n });\n }\n\n fetchRequest(state, chain, fields) {\n const url = this.getURL(state, chain, fields);\n const body = this.getCacheKey(state, chain, fields);\n const headers = {\n 'Content-Type': 'application/json',\n };\n\n return fetch(url, {method: 'POST', body: body, headers: headers}).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function (resp) {\n const json = typeof resp == 'string' ? JSON.parse(resp) : resp;\n if (json.error) {\n // RAREMETAL-server quirk: The API sometimes returns a 200 status code for failed requests,\n // with a human-readable error description as a key\n // For now, this should be treated strictly as an error\n throw new Error(json.error);\n }\n return json;\n });\n }\n\n annotateData(records, chain) {\n // Operate on the calculated results. The result of this method will be added to chain.discrete\n\n // In a page using live API data, the UI would only request the masks it needs from the API.\n // But in our demos, sometimes boilerplate JSON has more masks than the UI asked for. Limit what calcs we run (by\n // type, and to the set of groups requested by the user)\n\n // The Raremetal-server API has a quirk: it returns a different payload structure if no groups are defined\n // for the request region. Detect when that happens and end the calculation immediately in that case\n if (!records.groups) {\n return { groups: [], variants: [] };\n }\n\n records.groups = records.groups.filter(function (item) {\n return item.groupType === 'GENE';\n });\n\n const parsed = helpers.parsePortalJSON(records);\n let groups = parsed[0];\n const variants = parsed[1];\n // Some APIs may return more data than we want (eg simple sites that are just serving up premade scorecov json files).\n // Filter the response to just what the user has chosen to analyze.\n groups = groups.byMask(chain.header.aggregation_mask_ids);\n\n // Determine what calculations to run\n const calcs = chain.header.aggregation_calcs;\n if (!calcs || Object.keys(calcs).length === 0) {\n // If no calcs have been requested, then return a dummy placeholder immediately\n return { variants: [], groups: [], results: [] };\n }\n const runner = new helpers.PortalTestRunner(groups, variants, calcs);\n\n return runner.toJSON()\n .then(function (res) {\n // Internally, raremetal helpers track how the calculation is done, but not any display-friendly values\n // We will annotate each mask name (id) with a human-friendly description for later use\n const mask_id_to_desc = chain.header.aggregation_masks.reduce(function (acc, val) {\n acc[val.name] = val.description;\n return acc;\n }, {});\n res.data.groups.forEach(function (group) {\n group.mask_name = mask_id_to_desc[group.mask];\n });\n return res.data;\n })\n .catch(function (e) {\n console.error(e);\n throw new Error('Failed to calculate aggregation test results');\n });\n }\n\n normalizeResponse(data) {\n return data;\n }\n\n combineChainBody(records, chain) {\n // aggregation tests are a bit unique, in that the data is rarely used directly- instead it is used to annotate many\n // other layers in different ways. The calculated result has been added to `chain.discrete`, but will not be returned\n // as part of the response body built up by the chain\n return chain.body;\n }\n\n }\n\n /**\n * Restructure RAREMETAL-SERVER data used to calculate aggregation tests into a format that can be used to\n * display a GWAS scatter plot.\n * @see module:ext/lz-aggregation-tests\n * @see module:LocusZoom_Adapters\n * @private\n */\n class AssocFromAggregationLZ extends BaseAdapter {\n constructor(config) {\n if (!config || !config.from) {\n throw 'Must specify the name of the source that contains association data';\n }\n super(...arguments);\n }\n parseInit(config) {\n super.parseInit(config);\n this._from = config.from;\n }\n\n getRequest(state, chain, fields) {\n // Does not actually make a request. Just pick off the specific bundle of data from a known payload structure.\n if (chain.discrete && !chain.discrete[this._from]) {\n throw `${this.constructor.SOURCE_NAME} cannot be used before loading required data for: ${this._from}`;\n }\n // Copy the data so that mutations (like sorting) don't affect the original\n return Promise.resolve(JSON.parse(JSON.stringify(chain.discrete[this._from]['variants'])));\n }\n\n normalizeResponse(data) {\n // The payload structure of the association source is slightly different than the one required by association\n // plots. For example, we need to parse variant names and convert to log_pvalue\n const REGEX_EPACTS = new RegExp('(?:chr)?(.+):(\\\\d+)_?(\\\\w+)?/?([^_]+)?_?(.*)?'); // match API variant strings\n return data.map((one_variant) => {\n const match = one_variant.variant.match(REGEX_EPACTS);\n return {\n variant: one_variant.variant,\n chromosome: match[1],\n position: +match[2],\n ref_allele: match[3],\n ref_allele_freq: 1 - one_variant.altFreq,\n log_pvalue: -Math.log10(one_variant.pvalue),\n };\n }).sort((a, b) => {\n a = a.variant;\n b = b.variant;\n if (a < b) {\n return -1;\n } else if (a > b) {\n return 1;\n } else {\n // names must be equal\n return 0;\n }\n });\n }\n }\n\n /**\n * A sample connector that aligns calculated aggregation test data with corresponding gene information. Returns a body\n * suitable for use with the genes datalayer.\n *\n * To use this source, one must specify a fields array that calls first the genes source, then a dummy field from\n * this source. The output will be to transparently add several new fields to the genes data.\n * @see module:ext/lz-aggregation-tests\n * @see module:LocusZoom_Adapters\n * @private\n */\n class GeneAggregationConnectorLZ extends ConnectorSource {\n _getRequiredSources() {\n return ['gene_ns', 'aggregation_ns'];\n }\n\n combineChainBody(data, chain) {\n // The genes layer receives all results, and displays only the best pvalue for each gene\n\n // Tie the calculated group-test results to genes with a matching name\n const aggregation_source_id = this._source_name_mapping['aggregation_ns'];\n const gene_source_id = this._source_name_mapping['gene_ns'];\n // This connector assumes that genes are the main body of records from the chain, and that aggregation tests are\n // a standalone source that has not acted on genes data yet\n const aggregationData = chain.discrete[aggregation_source_id];\n const genesData = chain.discrete[gene_source_id];\n\n const groupedAggregation = {}; // Group together all tests done on that gene- any mask, any test\n\n aggregationData.groups.forEach(function (result) {\n if (!Object.prototype.hasOwnProperty.call(groupedAggregation, result.group)) {\n groupedAggregation[result.group] = [];\n }\n groupedAggregation[result.group].push(result.pvalue);\n });\n\n // Annotate any genes that have test results\n genesData.forEach(function (gene) {\n const gene_id = gene.gene_name;\n const tests = groupedAggregation[gene_id];\n if (tests) {\n gene.aggregation_best_pvalue = Math.min.apply(null, tests);\n }\n });\n return genesData;\n }\n }\n\n\n LocusZoom.Adapters.add('AggregationTestSourceLZ', AggregationTestSourceLZ);\n LocusZoom.Adapters.add('AssocFromAggregationLZ', AssocFromAggregationLZ);\n LocusZoom.Adapters.add('GeneAggregationConnectorLZ', GeneAggregationConnectorLZ);\n}\n\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"raremetal\"","webpack://[name]/./esm/ext/lz-aggregation-tests.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","raremetal","install","LocusZoom","BaseUrlAdapter","Adapters","DataFunctions","add","state","genes_data","aggregation_data","groupedAggregation","groups","forEach","result","group","push","pvalue","gene","gene_id","gene_name","tests","aggregation_best_pvalue","Math","min","apply","config","prefix_namespace","super","aggregation_tests","genoset_id","genoset_build","phenoset_build","pheno","calcs","masks","mask_ids","map","item","name","options","this","_url","chr","start","end","phenoset_id","JSON","stringify","chrom","stop","genotypeDataset","phenotypeDataset","phenotype","samples","genomeBuild","url","_getURL","body","_getCacheKey","fetch","method","headers","then","response","ok","Error","statusText","text","resp","json","parse","error","data","records","variants","filter","groupType","parsed","helpers","byMask","keys","length","results","toJSON","res","mask_id_to_desc","reduce","acc","val","description","mask_name","mask","catch","e","console","agg_results","_agg_results","Promise","resolve","REGEX_EPACTS","RegExp","variant","altFreq","match","_","chromosome","position","ref_allele","ref_allele_freq","log_pvalue","log10","sort","a","b","use"],"mappings":"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","file":"ext/lz-aggregation-tests.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = raremetal;","/**\n * LocusZoom extensions used to calculate and render aggregation test results. Because these calculations depend on an\n * external library, the special data adapters are defined here, rather than in LocusZoom core code.\n *\n * This extension provides a number of features that are closely tied to the aggregation tests demo,\n * and thus the specific UI and data operations are less of a drop-in generic addon than most other extensions.\n * This tool also depends on a calculation tool (like RAREMETAL-server) with access to sample specific genotypes.\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n * - raremetal.js (available via NPM or a related CDN)\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import aggTests from 'locuszoom/esm/ext/lz-aggregation-tests';\n * LocusZoom.use(aggTests);\n * ```\n *\n * Then use the layouts and data adapters made available by this extension. (see demos and documentation for guidance)\n * @private\n * @module\n */\n// This is defined as a UMD module, to work with multiple different module systems / bundlers\n// Arcane build note: everything defined here gets registered globally. This is not a \"pure\" module, and some build\n// systems may require being told that this file has side effects.\n\nimport {helpers} from 'raremetal.js';\n\nfunction install (LocusZoom) {\n const BaseUrlAdapter = LocusZoom.Adapters.get('BaseLZAdapter');\n\n /**\n * Calculates gene or region-based tests based on provided data, using the raremetal.js library.\n * It will rarely be used by itself, but rather using a connector that attaches the results to data from\n * another source (like genes). Using a separate connector allows us to add caching and run this front-end\n * calculation only once, while using it in many different places.\n * @see module:ext/lz-aggregation-tests\n * @private\n */\n class AggregationTestSourceLZ extends BaseUrlAdapter {\n constructor(config) {\n config.prefix_namespace = false;\n super(config);\n }\n\n _buildRequestOptions(state) {\n const { aggregation_tests = {} } = state;\n const {\n // eslint-disable-next-line no-unused-vars\n genoset_id = null, genoset_build = null, phenoset_build = null, pheno = null, calcs = {}, masks = [],\n } = aggregation_tests;\n\n aggregation_tests.mask_ids = masks.map((item) => item.name);\n // Many of these params will be undefined if no tests defined\n state.aggregation_tests = aggregation_tests;\n return state;\n }\n\n _getURL(options) {\n // Unlike most sources, calculations may require access to plot state data even after the initial request\n // This example source REQUIRES that the external UI widget would store the needed test definitions in a plot state\n // field called `aggregation_tests` (an object {masks: [], calcs: {})\n return this._url;\n }\n\n _getCacheKey(options) {\n const { chr, start, end, aggregation_tests } = options;\n const { genoset_id = null, genoset_build = null, phenoset_id = null, pheno = null, mask_ids } = aggregation_tests;\n\n return JSON.stringify({\n chrom: chr,\n start: start,\n stop: end,\n genotypeDataset: genoset_id,\n phenotypeDataset: phenoset_id,\n phenotype: pheno,\n samples: 'ALL',\n genomeBuild: genoset_build,\n masks: mask_ids,\n });\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n const body = this._getCacheKey(options); // cache key doubles as request body\n const headers = {\n 'Content-Type': 'application/json',\n };\n\n return fetch(url, {method: 'POST', body: body, headers: headers})\n .then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then((resp) => {\n const json = typeof resp == 'string' ? JSON.parse(resp) : resp;\n if (json.error) {\n // RAREMETAL-server quirk: The API sometimes returns a 200 status code for failed requests,\n // with a human-readable error description as a key\n // For now, this should be treated strictly as an error\n throw new Error(json.error);\n }\n return json.data;\n });\n }\n\n _annotateRecords(records, options) {\n // The server response gives covariance for a set of masks, but the actual calculations are done separately.\n // Eg, several types of aggtests might use the same covariance matrix.\n\n // TODO In practice, the test calculations are slow. Investigate caching full results (returning all from performRequest), not just covmat.\n // This code is largely for demonstration purposes and does not reflect modern best practices possible in LocusZoom (eg sources + dependencies to link together requests)\n const { aggregation_tests } = options;\n const { calcs = [], mask_ids = [], masks = [] } = aggregation_tests;\n\n // In a page using live API data, the UI would only request the masks it needs from the API.\n // But in our demos, sometimes boilerplate JSON has more masks than the UI asked for. Limit what calcs we run (by\n // type, and to the set of groups requested by the user)\n\n // The Raremetal-server API has a quirk: it returns a different payload structure if no groups are defined\n // for the request region. Detect when that happens and end the calculation immediately in that case\n if (!records.groups) {\n return { groups: [], variants: [] };\n }\n\n records.groups = records.groups.filter((item) => item.groupType === 'GENE');\n\n const parsed = helpers.parsePortalJSON(records);\n let groups = parsed[0];\n const variants = parsed[1];\n // Some APIs may return more data than we want (eg simple sites that are just serving up premade scorecov json files).\n // Filter the response to just what the user has chosen to analyze.\n groups = groups.byMask(mask_ids);\n\n // Determine what calculations to run\n if (!calcs || Object.keys(calcs).length === 0) {\n // If no calcs have been requested, then return a dummy placeholder immediately\n return { variants: [], groups: [], results: [] };\n }\n const runner = new helpers.PortalTestRunner(groups, variants, calcs);\n\n return runner.toJSON()\n .then(function (res) {\n // Internally, raremetal helpers track how the calculation is done, but not any display-friendly values\n // We will annotate each mask name (id) with a human-friendly description for later use\n const mask_id_to_desc = masks.reduce((acc, val) => {\n acc[val.name] = val.description;\n return acc;\n }, {});\n res.data.groups.forEach((group) => {\n group.mask_name = mask_id_to_desc[group.mask];\n });\n return res.data;\n })\n .catch(function (e) {\n console.error(e);\n throw new Error('Failed to calculate aggregation test results');\n });\n }\n }\n\n /**\n * Restructure RAREMETAL-SERVER data used to calculate aggregation tests into a format that can be used to\n * display a GWAS scatter plot.\n * @see module:ext/lz-aggregation-tests\n * @see module:LocusZoom_Adapters\n * @private\n */\n class AssocFromAggregationLZ extends BaseUrlAdapter {\n _buildRequestOptions(state, agg_results) {\n // This adapter just reformats an existing payload from cache (maybe this is better as a data_operation instead of an adapter nowadays?)\n if (!agg_results) {\n throw new Error('Aggregation test results must be provided');\n }\n state._agg_results = agg_results;\n return state;\n }\n\n _performRequest(options) {\n return Promise.resolve(options._agg_results['variants']);\n }\n\n _normalizeResponse(data) {\n // The payload structure of the association source is slightly different than the one required by association\n // plots. For example, we need to parse variant names and convert to log_pvalue\n const REGEX_EPACTS = new RegExp('(?:chr)?(.+):(\\\\d+)_?(\\\\w+)?/?([^_]+)?_?(.*)?'); // match API variant strings\n return data.map((item) => {\n const { variant, altFreq, pvalue } = item;\n const match = variant.match(REGEX_EPACTS);\n const [_, chromosome, position, ref_allele] = match;\n return {\n variant: variant,\n chromosome,\n position: +position,\n ref_allele,\n ref_allele_freq: 1 - altFreq,\n log_pvalue: -Math.log10(pvalue),\n };\n }).sort((a, b) => {\n a = a.variant;\n b = b.variant;\n if (a < b) {\n return -1;\n } else if (a > b) {\n return 1;\n } else {\n // names must be equal\n return 0;\n }\n });\n }\n }\n\n const genes_plus_aggregation = (state, [genes_data, aggregation_data]) => {\n // Used to highlight genes with significant aggtest results. Unlike a basic left join, it chooses one specific aggtest with the most significant results\n\n // Tie the calculated group-test results to genes with a matching name\n const groupedAggregation = {}; // Group together all tests done on that gene- any mask, any test\n\n aggregation_data.groups.forEach(function (result) {\n if (!Object.prototype.hasOwnProperty.call(groupedAggregation, result.group)) {\n groupedAggregation[result.group] = [];\n }\n groupedAggregation[result.group].push(result.pvalue);\n });\n\n // Annotate any genes that have test results\n genes_data.forEach((gene) => {\n const gene_id = gene.gene_name;\n const tests = groupedAggregation[gene_id];\n if (tests) {\n gene.aggregation_best_pvalue = Math.min.apply(null, tests);\n }\n });\n return genes_data;\n };\n LocusZoom.DataFunctions.add('gene_plus_aggregation', genes_plus_aggregation);\n\n LocusZoom.Adapters.add('AggregationTestSourceLZ', AggregationTestSourceLZ);\n LocusZoom.Adapters.add('AssocFromAggregationLZ', AssocFromAggregationLZ);\n}\n\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-credible-sets.min.js b/dist/ext/lz-credible-sets.min.js index 9b7edae7..2a62debf 100644 --- a/dist/ext/lz-credible-sets.min.js +++ b/dist/ext/lz-credible-sets.min.js @@ -1,3 +1,16 @@ -/*! Locuszoom 0.13.4 */ -var LzCredibleSets;(()=>{"use strict";var e={d:(a,s)=>{for(var t in s)e.o(s,t)&&!e.o(a,t)&&Object.defineProperty(a,t,{enumerable:!0,get:s[t]})},o:(e,a)=>Object.prototype.hasOwnProperty.call(e,a)},a={};e.d(a,{default:()=>o});const s=gwasCredibleSets;function t(e){const a=e.Adapters.get("BaseAdapter");e.Adapters.add("CredibleSetLZ",class extends a{constructor(e){super(...arguments),this.dependentSource=!0}parseInit(e){if(super.parseInit(...arguments),!this.params.fields||!this.params.fields.log_pvalue)throw new Error(`Source config for ${this.constructor.SOURCE_NAME} must specify how to find 'fields.log_pvalue'`);this.params=Object.assign({threshold:.95,significance_threshold:7.301},this.params)}getCacheKey(e,a,s){return[e.credible_set_threshold||this.params.threshold,e.chr,e.start,e.end].join("_")}fetchRequest(e,a){if(!a.body.length)return Promise.resolve([]);const t=this,o=e.credible_set_threshold||this.params.threshold;if(void 0===a.body[0][t.params.fields.log_pvalue])throw new Error("Credible set source could not locate the required fields from a previous request.");const i=a.body.map((e=>e[t.params.fields.log_pvalue]));if(!i.some((e=>e>=t.params.significance_threshold)))return Promise.resolve([]);const n=[];try{const e=s.scoring.bayesFactors(i),t=s.scoring.normalizeProbabilities(e),r=s.marking.findCredibleSet(t,o),c=s.marking.rescaleCredibleSet(r),l=s.marking.markBoolean(r);for(let e=0;e{{{{namespace[assoc]}}variant|htmlescape}}
P Value: {{{{namespace[assoc]}}log_pvalue|logtoscinotation|htmlescape}}
{{#if {{namespace[credset]}}posterior_prob}}
Posterior probability: {{{{namespace[credset]}}posterior_prob|scinotation|htmlescape}}{{/if}}"});const o=function(){const a=e.Layouts.get("data_layer","association_pvalues",{unnamespaced:!0,id:"associationcredibleset",namespace:{assoc:"assoc",credset:"credset",ld:"ld"},fill_opacity:.7,tooltip:e.Layouts.get("tooltip","association_credible_set",{unnamespaced:!0}),fields:["{{namespace[assoc]}}variant","{{namespace[assoc]}}position","{{namespace[assoc]}}log_pvalue","{{namespace[assoc]}}log_pvalue|logtoscinotation","{{namespace[assoc]}}ref_allele","{{namespace[credset]}}posterior_prob","{{namespace[credset]}}contrib_fraction","{{namespace[credset]}}is_member","{{namespace[ld]}}state","{{namespace[ld]}}isrefvar"],match:{send:"{{namespace[assoc]}}variant",receive:"{{namespace[assoc]}}variant"}});return a.color.unshift({field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#FFf000"}}),a}();e.Layouts.add("data_layer","association_credible_set",o);const i={namespace:{assoc:"assoc",credset:"credset"},id:"annotationcredibleset",type:"annotation_track",id_field:"{{namespace[assoc]}}variant",x_axis:{field:"{{namespace[assoc]}}position"},color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#001cee"}},"#00CC00"],fields:["{{namespace[assoc]}}variant","{{namespace[assoc]}}position","{{namespace[assoc]}}log_pvalue","{{namespace[credset]}}posterior_prob","{{namespace[credset]}}contrib_fraction","{{namespace[credset]}}is_member"],match:{send:"{{namespace[assoc]}}variant",receive:"{{namespace[assoc]}}variant"},filters:[{field:"{{namespace[credset]}}is_member",operator:"=",value:!0}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:e.Layouts.get("tooltip","annotation_credible_set",{unnamespaced:!0}),tooltip_positioning:"top"};e.Layouts.add("data_layer","annotation_credible_set",i);const n={id:"annotationcredibleset",title:{text:"SNPs in 95% credible set",x:50,style:{"font-size":"14px"}},min_height:50,height:50,margin:{top:25,right:50,bottom:10,left:50},inner_border:"rgb(210, 210, 210)",toolbar:e.Layouts.get("toolbar","standard_panel",{unnamespaced:!0}),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[e.Layouts.get("data_layer","annotation_credible_set",{unnamespaced:!0})]};e.Layouts.add("panel","annotation_credible_set",n);const r=function(){const a=e.Layouts.get("panel","association",{unnamespaced:!0,id:"associationcrediblesets",namespace:{assoc:"assoc",credset:"credset"},data_layers:[e.Layouts.get("data_layer","significance",{unnamespaced:!0}),e.Layouts.get("data_layer","recomb_rate",{unnamespaced:!0}),e.Layouts.get("data_layer","association_credible_set",{unnamespaced:!0})]});return a.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationcredibleset",default_config_display_name:"Linkage Disequilibrium (default)",options:[{display_name:"95% credible set (boolean)",display:{point_shape:"circle",point_size:40,color:{field:"{{namespace[credset]}}is_member",scale_function:"if",parameters:{field_value:!0,then:"#00CC00",else:"#CCCCCC"}},legend:[{shape:"circle",color:"#00CC00",size:40,label:"In credible set",class:"lz-data_layer-scatter"},{shape:"circle",color:"#CCCCCC",size:40,label:"Not in credible set",class:"lz-data_layer-scatter"}]}},{display_name:"95% credible set (gradient by contribution)",display:{point_shape:"circle",point_size:40,color:[{field:"{{namespace[credset]}}contrib_fraction",scale_function:"if",parameters:{field_value:0,then:"#777777"}},{scale_function:"interpolate",field:"{{namespace[credset]}}contrib_fraction",parameters:{breaks:[0,1],values:["#fafe87","#9c0000"]}}],legend:[{shape:"circle",color:"#777777",size:40,label:"No contribution",class:"lz-data_layer-scatter"},{shape:"circle",color:"#fafe87",size:40,label:"Some contribution",class:"lz-data_layer-scatter"},{shape:"circle",color:"#9c0000",size:40,label:"Most contribution",class:"lz-data_layer-scatter"}]}}]}),a}();e.Layouts.add("panel","association_credible_set",r);const c={state:{},width:800,height:450,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:e.Layouts.get("toolbar","standard_association",{unnamespaced:!0}),panels:[e.Layouts.get("panel","association_credible_set",{unnamespaced:!0}),e.Layouts.get("panel","annotation_credible_set",{unnamespaced:!0}),e.Layouts.get("panel","genes",{unnamespaced:!0})]};e.Layouts.add("plot","association_credible_set",c)}"undefined"!=typeof LocusZoom&&LocusZoom.use(t);const o=t;LzCredibleSets=a.default})(); +/*! Locuszoom 0.14.0-beta.1 */ +var LzCredibleSets;(()=>{var e={803:function(e){var t;t=function(){return function(e){var t={};function r(o){if(t[o])return t[o].exports;var a=t[o]={i:o,l:!1,exports:{}};return e[o].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(e,t,r){"use strict"; +/** + * @module stats + * @license MIT + * */ +function o(e){var t=[3.3871328727963665,133.14166789178438,1971.5909503065513,13731.69376550946,45921.95393154987,67265.7709270087,33430.57558358813,2509.0809287301227],r=[42.31333070160091,687.1870074920579,5394.196021424751,21213.794301586597,39307.89580009271,28729.085735721943,5226.495278852854],o=[1.4234371107496835,4.630337846156546,5.769497221460691,3.6478483247632045,1.2704582524523684,.2417807251774506,.022723844989269184,.0007745450142783414],a=[2.053191626637759,1.6763848301838038,.6897673349851,.14810397642748008,.015198666563616457,.0005475938084995345,1.0507500716444169e-9],i=[6.657904643501103,5.463784911164114,1.7848265399172913,.29656057182850487,.026532189526576124,.0012426609473880784,27115555687434876e-21,2.0103343992922881e-7],n=[.599832206555888,.1369298809227358,.014875361290850615,.0007868691311456133,18463183175100548e-21,1.421511758316446e-7,20442631033899397e-31],s=e-.5,l=void 0,c=void 0;if(Math.abs(s)<.425)return s*(((((((t[7]*(l=.180625-s*s)+t[6])*l+t[5])*l+t[4])*l+t[3])*l+t[2])*l+t[1])*l+t[0])/(((((((r[6]*l+r[5])*l+r[4])*l+r[3])*l+r[2])*l+r[1])*l+r[0])*l+1);if(!((l=s<0?e:1-e)>0))throw"Not implemented";return c=(l=Math.sqrt(-Math.log(l)))<=5?(((((((o[7]*(l-=1.6)+o[6])*l+o[5])*l+o[4])*l+o[3])*l+o[2])*l+o[1])*l+o[0])/(((((((a[6]*l+a[5])*l+a[4])*l+a[3])*l+a[2])*l+a[1])*l+a[0])*l+1):(((((((i[7]*(l-=5)+i[6])*l+i[5])*l+i[4])*l+i[3])*l+i[2])*l+i[1])*l+i[0])/(((((((n[6]*l+n[5])*l+n[4])*l+n[3])*l+n[2])*l+n[1])*l+n[0])*l+1),s<0&&(c=-c),c}Object.defineProperty(t,"__esModule",{value:!0});var a={ninv:o};t.ninv=o,t.default=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.marking=t.stats=t.scoring=void 0;var o=n(r(0)),a=n(r(2)),i=n(r(3));function n(e){return e&&e.__esModule?e:{default:e}}t.scoring=a.default,t.stats=o.default,t.marking=i.default},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t._nlogp_to_z2=t.normalizeProbabilities=t.bayesFactors=void 0;var o=r(0);function a(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t1&&void 0!==arguments[1])||arguments[1];if(!Array.isArray(e)||!e.length)throw"Must provide a non-empty array of pvalues";var r=e.map((function(e){return i(e)/2}));if(t){var o=Math.max.apply(Math,a(r))-708;o>0&&(r=r.map((function(e){return e-o})))}return r.map((function(e){return Math.exp(e)}))}function s(e){var t=e.reduce((function(e,t){return e+t}),0);return e.map((function(e){return e/t}))}var l={bayesFactors:n,normalizeProbabilities:s};t.default=l,t.bayesFactors=n,t.normalizeProbabilities=s,t._nlogp_to_z2=i},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=function(e,t){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return function(e,t){var r=[],o=!0,a=!1,i=void 0;try{for(var n,s=e[Symbol.iterator]();!(o=(n=s.next()).done)&&(r.push(n.value),!t||r.length!==t);o=!0);}catch(e){a=!0,i=e}finally{try{!o&&s.return&&s.return()}finally{if(a)throw i}}return r}(e,t);throw new TypeError("Invalid attempt to destructure non-iterable instance")}; +/** + * @module marking + * @license MIT + */function a(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:.95;if(!Array.isArray(e)||!e.length)throw"Probs must be a non-empty array";if("number"!=typeof t||t<0||t>1||Number.isNaN(t))throw"Cutoff must be a number between 0 and 1";var r=e.reduce((function(e,t){return e+t}),0);if(r<=0)throw"Sum of provided probabilities must be > 0";for(var a=e.map((function(e,t){return[e,t]})).sort((function(e,t){return t[0]-e[0]})),i=0,n=new Array(a.length).fill(0),s=0;s{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var o={};(()=>{"use strict";r.d(o,{default:()=>a});var e=r(803);function t(t){const r=t.Adapters.get("BaseUMAdapter");t.Adapters.add("CredibleSetLZ",class extends r{constructor(e){super(...arguments),this._config=Object.assign({threshold:.95,significance_threshold:7.301},this._config),this._prefix_namespace=!1}_getCacheKey(e){return[e.credible_set_threshold||this._config.threshold,e.chr,e.start,e.end].join("_")}_buildRequestOptions(e,t){const r=super._buildRequestOptions(...arguments);return r._assoc_data=t,r}_performRequest(t){const{_assoc_data:r}=t;if(!r.length)return Promise.resolve([]);const o=this._findPrefixedKey(r[0],"log_pvalue"),a=this._config.threshold,i=r.map((e=>e[o]));if(!i.some((e=>e>=this._config.significance_threshold)))return Promise.resolve(r);try{const o=e.scoring.bayesFactors(i),n=e.scoring.normalizeProbabilities(o),s=e.marking.findCredibleSet(n,a),l=e.marking.rescaleCredibleSet(s),c=e.marking.markBoolean(s);for(let e=0;e{{assoc:variant|htmlescape}}
P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
{{#if credset:posterior_prob}}
Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}"});const a=function(){const e=t.Layouts.get("data_layer","association_pvalues",{id:"associationcredibleset",namespace:{assoc:"assoc",credset:"credset",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)","credset(assoc)"]},{type:"left_match",name:"credset_plus_ld",requires:["credset","ld"],params:["assoc:position","ld:position2"]}],fill_opacity:.7,tooltip:t.Layouts.get("tooltip","association_credible_set"),match:{send:"assoc:variant",receive:"assoc:variant"}});return e.color.unshift({field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#FFf000"}}),e}();t.Layouts.add("data_layer","association_credible_set",a);const i={namespace:{assoc:"assoc",credset:"credset"},data_operations:[{type:"fetch",from:["assoc","credset(assoc)"]}],id:"annotationcredibleset",type:"annotation_track",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#001cee"}},"#00CC00"],match:{send:"assoc:variant",receive:"assoc:variant"},filters:[{field:"credset:is_member",operator:"=",value:!0}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:t.Layouts.get("tooltip","annotation_credible_set"),tooltip_positioning:"top"};t.Layouts.add("data_layer","annotation_credible_set",i);const n={id:"annotationcredibleset",title:{text:"SNPs in 95% credible set",x:50,style:{"font-size":"14px"}},min_height:50,height:50,margin:{top:25,right:50,bottom:10,left:50},inner_border:"rgb(210, 210, 210)",toolbar:t.Layouts.get("toolbar","standard_panel"),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[t.Layouts.get("data_layer","annotation_credible_set")]};t.Layouts.add("panel","annotation_credible_set",n);const s=function(){const e=t.Layouts.get("panel","association",{id:"associationcrediblesets",data_layers:[t.Layouts.get("data_layer","significance"),t.Layouts.get("data_layer","recomb_rate"),t.Layouts.get("data_layer","association_credible_set")]});return e.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationcredibleset",default_config_display_name:"Linkage Disequilibrium (default)",options:[{display_name:"95% credible set (boolean)",display:{point_shape:"circle",point_size:40,color:{field:"credset:is_member",scale_function:"if",parameters:{field_value:!0,then:"#00CC00",else:"#CCCCCC"}},legend:[{shape:"circle",color:"#00CC00",size:40,label:"In credible set",class:"lz-data_layer-scatter"},{shape:"circle",color:"#CCCCCC",size:40,label:"Not in credible set",class:"lz-data_layer-scatter"}]}},{display_name:"95% credible set (gradient by contribution)",display:{point_shape:"circle",point_size:40,color:[{field:"credset:contrib_fraction",scale_function:"if",parameters:{field_value:0,then:"#777777"}},{scale_function:"interpolate",field:"credset:contrib_fraction",parameters:{breaks:[0,1],values:["#fafe87","#9c0000"]}}],legend:[{shape:"circle",color:"#777777",size:40,label:"No contribution",class:"lz-data_layer-scatter"},{shape:"circle",color:"#fafe87",size:40,label:"Some contribution",class:"lz-data_layer-scatter"},{shape:"circle",color:"#9c0000",size:40,label:"Most contribution",class:"lz-data_layer-scatter"}]}}]}),e}();t.Layouts.add("panel","association_credible_set",s);const l={state:{},width:800,height:450,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association"),panels:[t.Layouts.get("panel","association_credible_set"),t.Layouts.get("panel","annotation_credible_set"),t.Layouts.get("panel","genes")]};t.Layouts.add("plot","association_credible_set",l)}"undefined"!=typeof LocusZoom&&LocusZoom.use(t);const a=t})(),LzCredibleSets=o.default})(); //# sourceMappingURL=lz-credible-sets.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-credible-sets.min.js.map b/dist/ext/lz-credible-sets.min.js.map index 8d480d88..1cf01178 100644 --- a/dist/ext/lz-credible-sets.min.js.map +++ b/dist/ext/lz-credible-sets.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"gwasCredibleSets\"","webpack://[name]/./esm/ext/lz-credible-sets.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","gwasCredibleSets","install","LocusZoom","BaseAdapter","Adapters","add","config","super","arguments","this","dependentSource","parseInit","params","fields","log_pvalue","Error","constructor","SOURCE_NAME","assign","threshold","significance_threshold","state","chain","credible_set_threshold","chr","start","end","join","body","length","Promise","resolve","self","nlogpvals","map","item","some","val","credset_data","scores","scoring","posteriorProbabilities","credibleSet","marking","credSetScaled","credSetBool","i","push","posterior_prob","contrib_fraction","is_member","e","console","error","data","outnames","trans","src","dest","keys","forEach","attr","association_credible_set_tooltip","l","Layouts","unnamespaced","html","namespace","closable","show","or","hide","and","association_credible_set_layer","base","id","fill_opacity","tooltip","match","send","receive","color","unshift","field","scale_function","parameters","field_value","then","annotation_credible_set_layer","type","id_field","x_axis","filters","operator","value","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip_positioning","annotation_credible_set","title","text","x","style","min_height","height","margin","top","right","bottom","left","inner_border","toolbar","axes","extent","render","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","data_layers","association_credible_set_panel","widgets","position","button_html","button_title","layer_name","default_config_display_name","options","display_name","display","point_shape","point_size","else","legend","shape","size","label","class","breaks","values","association_credible_set_plot","width","responsive_resize","min_region_scale","max_region_scale","panels","use"],"mappings":";sCACA,IAAIA,EAAsB,CCA1B,EAAwB,CAACC,EAASC,KACjC,IAAI,IAAIC,KAAOD,EACXF,EAAoBI,EAAEF,EAAYC,KAASH,EAAoBI,EAAEH,EAASE,IAC5EE,OAAOC,eAAeL,EAASE,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,MCJ3E,EAAwB,CAACM,EAAKC,IAAUL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,I,4BCAlF,MAAM,EAA+BI,iBCmCrC,SAASC,EAASC,GACd,MAAMC,EAAcD,EAAUE,SAASV,IAAI,eAwG3CQ,EAAUE,SAASC,IAAI,gBA/FvB,cAA4BF,EAUxB,YAAYG,GACRC,SAASC,WACTC,KAAKC,iBAAkB,EAG3B,UAAUJ,GAEN,GADAC,MAAMI,aAAaH,YACbC,KAAKG,OAAOC,SAAUJ,KAAKG,OAAOC,OAAOC,WAC3C,MAAM,IAAIC,MAAM,qBAAqBN,KAAKO,YAAYC,4DAI1DR,KAAKG,OAASrB,OAAO2B,OACjB,CAAEC,UAAW,IAAMC,uBAAwB,OAC3CX,KAAKG,QAIb,YAAaS,EAAOC,EAAOT,GAEvB,MAAO,CADWQ,EAAME,wBAA0Bd,KAAKG,OAAOO,UAC3CE,EAAMG,IAAKH,EAAMI,MAAOJ,EAAMK,KAAKC,KAAK,KAG/D,aAAaN,EAAOC,GAChB,IAAKA,EAAMM,KAAKC,OAEZ,OAAOC,QAAQC,QAAQ,IAG3B,MAAMC,EAAOvB,KAEPU,EAAYE,EAAME,wBAA0Bd,KAAKG,OAAOO,UAE9D,QAA4D,IAAjDG,EAAMM,KAAK,GAAGI,EAAKpB,OAAOC,OAAOC,YACxC,MAAM,IAAIC,MAAM,qFAEpB,MAAMkB,EAAYX,EAAMM,KAAKM,KAAKC,GAASA,EAAKH,EAAKpB,OAAOC,OAAOC,cAEnE,IAAKmB,EAAUG,MAAMC,GAAQA,GAAOL,EAAKpB,OAAOQ,yBAG5C,OAAOU,QAAQC,QAAQ,IAG3B,MAAMO,EAAe,GACrB,IACI,MAAMC,EAAS,EAAAC,QAAA,aAAqBP,GAC9BQ,EAAyB,EAAAD,QAAA,uBAA+BD,GAIxDG,EAAc,EAAAC,QAAA,gBAAwBF,EAAwBtB,GAC9DyB,EAAgB,EAAAD,QAAA,mBAA2BD,GAC3CG,EAAc,EAAAF,QAAA,YAAoBD,GAGxC,IAAK,IAAII,EAAI,EAAGA,EAAIxB,EAAMM,KAAKC,OAAQiB,IACnCR,EAAaS,KAAK,CACdC,eAAgBP,EAAuBK,GACvCG,iBAAkBL,EAAcE,GAChCI,UAAWL,EAAYC,KAGjC,MAAOK,GAELC,QAAQC,MAAMF,GAElB,OAAOrB,QAAQC,QAAQO,GAG3B,iBAAiBgB,EAAMhC,EAAOT,EAAQ0C,EAAUC,GAE5C,GAAIlC,EAAMM,KAAKC,QAAUyB,EAAKzB,OAC1B,IAAK,IAAIiB,EAAI,EAAGA,EAAIQ,EAAKzB,OAAQiB,IAAK,CAClC,MAAMW,EAAMH,EAAKR,GACXY,EAAOpC,EAAMM,KAAKkB,GACxBvD,OAAOoE,KAAKF,GAAKG,SAAQ,SAAUC,GAC/BH,EAAKG,GAAQJ,EAAII,MAI7B,OAAOvC,EAAMM,QAarB,MAAMkC,EAAmC,WAErC,MAAMC,EAAI7D,EAAU8D,QAAQtE,IAAI,UAAW,uBAAwB,CAAEuE,cAAc,IAEnF,OADAF,EAAEG,MAAQ,iKACHH,EAJ8B,GAOzC7D,EAAU8D,QAAQ3D,IAAI,UAAW,2BAA4ByD,GAiB7D5D,EAAU8D,QAAQ3D,IAAI,UAAW,0BATO,CACpC8D,UAAW,CAAE,MAAS,QAAS,QAAW,WAC1CC,UAAU,EACVC,KAAM,CAAEC,GAAI,CAAC,cAAe,aAC5BC,KAAM,CAAEC,IAAK,CAAC,gBAAiB,eAC/BN,KAAM,8TAaV,MAAMO,EAAiC,WACnC,MAAMC,EAAOxE,EAAU8D,QAAQtE,IAAI,aAAc,sBAAuB,CACpEuE,cAAc,EACdU,GAAI,yBACJR,UAAW,CAAE,MAAS,QAAS,QAAW,UAAW,GAAM,MAC3DS,aAAc,GACdC,QAAS3E,EAAU8D,QAAQtE,IAAI,UAAW,2BAA4B,CAAEuE,cAAc,IACtFpD,OAAQ,CACJ,8BAA+B,+BAC/B,iCAAkC,kDAClC,iCACA,uCAAwC,yCACxC,kCACA,yBAA0B,6BAE9BiE,MAAO,CAAEC,KAAM,8BAA+BC,QAAS,iCAU3D,OARAN,EAAKO,MAAMC,QAAQ,CACfC,MAAO,cACPC,eAAgB,KAChBC,WAAY,CACRC,aAAa,EACbC,KAAM,aAGPb,EAzB4B,GA2BvCxE,EAAU8D,QAAQ3D,IAAI,aAAc,2BAA4BoE,GAQhE,MAAMe,EAAgC,CAClCrB,UAAW,CAAE,MAAS,QAAS,QAAW,WAC1CQ,GAAI,wBACJc,KAAM,mBACNC,SAAU,8BACVC,OAAQ,CACJR,MAAO,gCAEXF,MAAO,CACH,CACIE,MAAO,cACPC,eAAgB,KAChBC,WAAY,CACRC,aAAa,EACbC,KAAM,YAGd,WAEJ1E,OAAQ,CAAC,8BAA+B,+BAAgC,iCAAkC,uCAAwC,yCAA0C,mCAC5LiE,MAAO,CAAEC,KAAM,8BAA+BC,QAAS,+BACvDY,QAAS,CAEL,CAAET,MAAO,kCAAmCU,SAAU,IAAKC,OAAO,IAEtEC,UAAW,CACPC,YAAa,CACT,CAAEC,OAAQ,MAAOC,OAAQ,gBAE7BC,WAAY,CACR,CAAEF,OAAQ,QAASC,OAAQ,gBAE/BE,QAAS,CACL,CAAEH,OAAQ,SAAUC,OAAQ,WAAYG,WAAW,IAEvDC,aAAc,CACV,CAAEL,OAAQ,SAAUC,OAAQ,cAGpCrB,QAAS3E,EAAU8D,QAAQtE,IAAI,UAAW,0BAA2B,CAAEuE,cAAc,IACrFsC,oBAAqB,OAEzBrG,EAAU8D,QAAQ3D,IAAI,aAAc,0BAA2BmF,GAQ/D,MAAMgB,EAA0B,CAC5B7B,GAAI,wBACJ8B,MAAO,CAAEC,KAAM,2BAA4BC,EAAG,GAAIC,MAAO,CAAE,YAAa,SACxEC,WAAY,GACZC,OAAQ,GACRC,OAAQ,CAAEC,IAAK,GAAIC,MAAO,GAAIC,OAAQ,GAAIC,KAAM,IAChDC,aAAc,qBACdC,QAASnH,EAAU8D,QAAQtE,IAAI,UAAW,iBAAkB,CAAEuE,cAAc,IAC5EqD,KAAM,CACFX,EAAG,CAAEY,OAAQ,QAASC,QAAQ,IAElCC,YAAa,CACTC,wBAAwB,EACxBC,gBAAgB,EAChBC,UAAU,GAEdC,YAAa,CACT3H,EAAU8D,QAAQtE,IAAI,aAAc,0BAA2B,CAAEuE,cAAc,MAGvF/D,EAAU8D,QAAQ3D,IAAI,QAAS,0BAA2BmG,GAQ1D,MAAMsB,EAAiC,WACnC,MAAM/D,EAAI7D,EAAU8D,QAAQtE,IAAI,QAAS,cAAe,CACpDuE,cAAc,EACdU,GAAI,0BACJR,UAAW,CAAE,MAAS,QAAS,QAAW,WAC1C0D,YAAa,CACT3H,EAAU8D,QAAQtE,IAAI,aAAc,eAAgB,CAAEuE,cAAc,IACpE/D,EAAU8D,QAAQtE,IAAI,aAAc,cAAe,CAAEuE,cAAc,IACnE/D,EAAU8D,QAAQtE,IAAI,aAAc,2BAA4B,CAAEuE,cAAc,OAqGxF,OAjGAF,EAAEsD,QAAQU,QAAQhF,KACd,CACI0C,KAAM,kBACNuC,SAAU,QACV/C,MAAO,OAEPgD,YAAa,qBACbC,aAAc,uCACdC,WAAY,yBACZC,4BAA6B,mCAE7BC,QAAS,CACL,CAEIC,aAAc,6BACdC,QAAS,CACLC,YAAa,SACbC,WAAY,GACZxD,MAAO,CACHE,MAAO,kCACPC,eAAgB,KAChBC,WAAY,CACRC,aAAa,EACbC,KAAM,UACNmD,KAAM,YAGdC,OAAQ,CACJ,CACIC,MAAO,SACP3D,MAAO,UACP4D,KAAM,GACNC,MAAO,kBACPC,MAAO,yBAEX,CACIH,MAAO,SACP3D,MAAO,UACP4D,KAAM,GACNC,MAAO,sBACPC,MAAO,4BAKvB,CAEIT,aAAc,8CACdC,QAAS,CACLC,YAAa,SACbC,WAAY,GACZxD,MAAO,CACH,CACIE,MAAO,yCACPC,eAAgB,KAChBC,WAAY,CACRC,YAAa,EACbC,KAAM,YAGd,CACIH,eAAgB,cAChBD,MAAO,yCACPE,WAAY,CACR2D,OAAQ,CAAC,EAAG,GACZC,OAAQ,CAAC,UAAW,cAIhCN,OAAQ,CACJ,CACIC,MAAO,SACP3D,MAAO,UACP4D,KAAM,GACNC,MAAO,kBACPC,MAAO,yBAEX,CACIH,MAAO,SACP3D,MAAO,UACP4D,KAAM,GACNC,MAAO,oBACPC,MAAO,yBAEX,CACIH,MAAO,SACP3D,MAAO,UACP4D,KAAM,GACNC,MAAO,oBACPC,MAAO,+BAQ5BhF,EA7G4B,GA+GvC7D,EAAU8D,QAAQ3D,IAAI,QAAS,2BAA4ByH,GAQ3D,MAAMoB,EAAgC,CAClC7H,MAAO,GACP8H,MAAO,IACPrC,OAAQ,IACRsC,mBAAmB,EACnBC,iBAAkB,IAClBC,iBAAkB,IAClBjC,QAASnH,EAAU8D,QAAQtE,IAAI,UAAW,uBAAwB,CAAEuE,cAAc,IAClFsF,OAAQ,CACJrJ,EAAU8D,QAAQtE,IAAI,QAAS,2BAA4B,CAAEuE,cAAc,IAC3E/D,EAAU8D,QAAQtE,IAAI,QAAS,0BAA2B,CAAEuE,cAAc,IAC1E/D,EAAU8D,QAAQtE,IAAI,QAAS,QAAS,CAAEuE,cAAc,MAGhE/D,EAAU8D,QAAQ3D,IAAI,OAAQ,2BAA4B6I,GAIrC,oBAAdhJ,WAGPA,UAAUsJ,IAAIvJ,GAIlB,U","file":"ext/lz-credible-sets.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = gwasCredibleSets;","/**\n * Custom code used to power credible sets demonstration example. This is not part of the core LocusZoom library,\n * but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~CredibleSetLZ}\n * * {@link module:LocusZoom_Layouts~association_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_layer}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set_plot}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n * - gwas-credible-sets (available via NPM or a related CDN)\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import credibleSets from 'locuszoom/esm/ext/lz-credible-sets';\n * LocusZoom.use(credibleSets);\n * ```\n @module\n*/\n\nimport {marking, scoring} from 'gwas-credible-sets';\n\nfunction install (LocusZoom) {\n const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter');\n\n /**\n * (**extension**) Custom data adapter that calculates the 95% credible set based on provided association data.\n * This source must be requested as the second step in a chain, after a previous step that returns fields required\n * for the calculation. (usually, it follows a request for GWAS summary statistics)\n * @alias module:LocusZoom_Adapters~CredibleSetLZ\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n class CredibleSetLZ extends BaseAdapter {\n /**\n * @param {String} config.params.fields.log_pvalue The name of the field containing -log10 pvalue information\n * @param {Number} [config.params.threshold=0.95] The credible set threshold (eg 95%). Will continue selecting SNPs\n * until the posterior probabilities add up to at least this fraction of the total.\n * @param {Number} [config.params.significance_threshold=7.301] Do not perform a credible set calculation for this\n * region unless AT LEAST ONE SNP (as -log10p) exceeds the line of GWAS signficance. Otherwise we are declaring a\n * credible set when there is no evidence of anything being significant at all. If one snp is significant, it will\n * create a credible set for the entire region; the resulting set may include things below the line of significance.\n */\n constructor(config) {\n super(...arguments);\n this.dependentSource = true; // Don't do calcs for a region with no assoc data\n }\n\n parseInit(config) {\n super.parseInit(...arguments);\n if (!(this.params.fields && this.params.fields.log_pvalue)) {\n throw new Error(`Source config for ${this.constructor.SOURCE_NAME} must specify how to find 'fields.log_pvalue'`);\n }\n\n // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p)\n this.params = Object.assign(\n { threshold: 0.95, significance_threshold: 7.301 },\n this.params\n );\n }\n\n getCacheKey (state, chain, fields) {\n const threshold = state.credible_set_threshold || this.params.threshold;\n return [threshold, state.chr, state.start, state.end].join('_');\n }\n\n fetchRequest(state, chain) {\n if (!chain.body.length) {\n // No credible set can be calculated because there is no association data for this region\n return Promise.resolve([]);\n }\n\n const self = this;\n // The threshold can be overridden dynamically via `plot.state`, or set when the source is created\n const threshold = state.credible_set_threshold || this.params.threshold;\n // Calculate raw bayes factors and posterior probabilities based on information returned from the API\n if (typeof chain.body[0][self.params.fields.log_pvalue] === 'undefined') {\n throw new Error('Credible set source could not locate the required fields from a previous request.');\n }\n const nlogpvals = chain.body.map((item) => item[self.params.fields.log_pvalue]);\n\n if (!nlogpvals.some((val) => val >= self.params.significance_threshold)) {\n // If NO points have evidence of significance, define the credible set to be empty\n // (rather than make a credible set that we don't think is meaningful)\n return Promise.resolve([]);\n }\n\n const credset_data = [];\n try {\n const scores = scoring.bayesFactors(nlogpvals);\n const posteriorProbabilities = scoring.normalizeProbabilities(scores);\n\n // Use scores to mark the credible set in various ways (depending on your visualization preferences,\n // some of these may not be needed)\n const credibleSet = marking.findCredibleSet(posteriorProbabilities, threshold);\n const credSetScaled = marking.rescaleCredibleSet(credibleSet);\n const credSetBool = marking.markBoolean(credibleSet);\n\n // Annotate each response record based on credible set membership\n for (let i = 0; i < chain.body.length; i++) {\n credset_data.push({\n posterior_prob: posteriorProbabilities[i],\n contrib_fraction: credSetScaled[i],\n is_member: credSetBool[i],\n });\n }\n } catch (e) {\n // If the calculation cannot be completed, return the data without annotation fields\n console.error(e);\n }\n return Promise.resolve(credset_data);\n }\n\n combineChainBody(data, chain, fields, outnames, trans) {\n // At this point namespacing has been applied; add the calculated fields for this source to the chain\n if (chain.body.length && data.length) {\n for (let i = 0; i < data.length; i++) {\n const src = data[i];\n const dest = chain.body[i];\n Object.keys(src).forEach(function (attr) {\n dest[attr] = src[attr];\n });\n }\n }\n return chain.body;\n }\n }\n\n LocusZoom.Adapters.add('CredibleSetLZ', CredibleSetLZ);\n\n // Add related layouts to the central global registry\n /**\n * (**extension**) Tooltip layout that appends credible set posterior probability to the default association tooltip (for SNPs in the credible set)\n * @alias module:LocusZoom_Layouts~association_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_tooltip = function () {\n // Extend a known tooltip with an extra row of info showing posterior probabilities\n const l = LocusZoom.Layouts.get('tooltip', 'standard_association', { unnamespaced: true });\n l.html += '{{#if {{namespace[credset]}}posterior_prob}}
Posterior probability: {{{{namespace[credset]}}posterior_prob|scinotation|htmlescape}}{{/if}}';\n return l;\n }();\n\n LocusZoom.Layouts.add('tooltip', 'association_credible_set', association_credible_set_tooltip);\n\n /**\n * (**extension**) A tooltip layout for annotation (rug) tracks that provides information about credible set members\n * @alias module:LocusZoom_Layouts~annotation_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_tooltip = {\n namespace: { 'assoc': 'assoc', 'credset': 'credset' },\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{{{namespace[assoc]}}variant|htmlescape}}
'\n + 'P Value: {{{{namespace[assoc]}}log_pvalue|logtoscinotation|htmlescape}}
' +\n '{{#if {{namespace[credset]}}posterior_prob}}
Posterior probability: {{{{namespace[credset]}}posterior_prob|scinotation|htmlescape}}{{/if}}',\n };\n LocusZoom.Layouts.add('tooltip', 'annotation_credible_set', annotation_credible_set_tooltip);\n\n /**\n * (**extension**) A data layer layout that shows GWAS summary statistics overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n\n const association_credible_set_layer = function () {\n const base = LocusZoom.Layouts.get('data_layer', 'association_pvalues', {\n unnamespaced: true,\n id: 'associationcredibleset',\n namespace: { 'assoc': 'assoc', 'credset': 'credset', 'ld': 'ld' },\n fill_opacity: 0.7,\n tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set', { unnamespaced: true }),\n fields: [\n '{{namespace[assoc]}}variant', '{{namespace[assoc]}}position',\n '{{namespace[assoc]}}log_pvalue', '{{namespace[assoc]}}log_pvalue|logtoscinotation',\n '{{namespace[assoc]}}ref_allele',\n '{{namespace[credset]}}posterior_prob', '{{namespace[credset]}}contrib_fraction',\n '{{namespace[credset]}}is_member',\n '{{namespace[ld]}}state', '{{namespace[ld]}}isrefvar',\n ],\n match: { send: '{{namespace[assoc]}}variant', receive: '{{namespace[assoc]}}variant' },\n });\n base.color.unshift({\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#FFf000',\n },\n });\n return base;\n }();\n LocusZoom.Layouts.add('data_layer', 'association_credible_set', association_credible_set_layer);\n\n /**\n * (**extension**) A data layer layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_layer = {\n namespace: { 'assoc': 'assoc', 'credset': 'credset' },\n id: 'annotationcredibleset',\n type: 'annotation_track',\n id_field: '{{namespace[assoc]}}variant',\n x_axis: {\n field: '{{namespace[assoc]}}position',\n },\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#001cee',\n },\n },\n '#00CC00',\n ],\n fields: ['{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', '{{namespace[assoc]}}log_pvalue', '{{namespace[credset]}}posterior_prob', '{{namespace[credset]}}contrib_fraction', '{{namespace[credset]}}is_member'],\n match: { send: '{{namespace[assoc]}}variant', receive: '{{namespace[assoc]}}variant' },\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: '{{namespace[credset]}}is_member', operator: '=', value: true },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: LocusZoom.Layouts.get('tooltip', 'annotation_credible_set', { unnamespaced: true }),\n tooltip_positioning: 'top',\n };\n LocusZoom.Layouts.add('data_layer', 'annotation_credible_set', annotation_credible_set_layer);\n\n /**\n * (**extension**) A panel layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set = {\n id: 'annotationcredibleset',\n title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } },\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 50, bottom: 10, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel', { unnamespaced: true }),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'annotation_credible_set', { unnamespaced: true }),\n ],\n };\n LocusZoom.Layouts.add('panel', 'annotation_credible_set', annotation_credible_set);\n\n /**\n * (**extension**) A panel layout that shows GWAS summary statistics in a standard LocusZoom view, overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_panel = function () {\n const l = LocusZoom.Layouts.get('panel', 'association', {\n unnamespaced: true,\n id: 'associationcrediblesets',\n namespace: { 'assoc': 'assoc', 'credset': 'credset' },\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'significance', { unnamespaced: true }),\n LocusZoom.Layouts.get('data_layer', 'recomb_rate', { unnamespaced: true }),\n LocusZoom.Layouts.get('data_layer', 'association_credible_set', { unnamespaced: true }),\n ],\n });\n // Add \"display options\" button to control how credible set coloring is overlaid on the standard association plot\n l.toolbar.widgets.push(\n {\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n layer_name: 'associationcredibleset',\n default_config_display_name: 'Linkage Disequilibrium (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: '95% credible set (boolean)', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n point_shape: 'circle',\n point_size: 40,\n color: {\n field: '{{namespace[credset]}}is_member',\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#00CC00',\n else: '#CCCCCC',\n },\n },\n legend: [ // Tells the legend how to represent this display option\n {\n shape: 'circle',\n color: '#00CC00',\n size: 40,\n label: 'In credible set',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#CCCCCC',\n size: 40,\n label: 'Not in credible set',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n {\n // Second option. The same plot- or even the same field- can be colored in more than one way.\n display_name: '95% credible set (gradient by contribution)',\n display: {\n point_shape: 'circle',\n point_size: 40,\n color: [\n {\n field: '{{namespace[credset]}}contrib_fraction',\n scale_function: 'if',\n parameters: {\n field_value: 0,\n then: '#777777',\n },\n },\n {\n scale_function: 'interpolate',\n field: '{{namespace[credset]}}contrib_fraction',\n parameters: {\n breaks: [0, 1],\n values: ['#fafe87', '#9c0000'],\n },\n },\n ],\n legend: [\n {\n shape: 'circle',\n color: '#777777',\n size: 40,\n label: 'No contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#fafe87',\n size: 40,\n label: 'Some contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#9c0000',\n size: 40,\n label: 'Most contribution',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n ],\n }\n );\n return l;\n }();\n LocusZoom.Layouts.add('panel', 'association_credible_set', association_credible_set_panel);\n\n /**\n * (**extension**) A standard LocusZoom plot layout, with additional credible set information.\n * @alias module:LocusZoom_Layouts~association_credible_set_plot\n * @type plot\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_plot = {\n state: {},\n width: 800,\n height: 450,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association', { unnamespaced: true }),\n panels: [\n LocusZoom.Layouts.get('panel', 'association_credible_set', { unnamespaced: true }),\n LocusZoom.Layouts.get('panel', 'annotation_credible_set', { unnamespaced: true }),\n LocusZoom.Layouts.get('panel', 'genes', { unnamespaced: true }),\n ],\n };\n LocusZoom.Layouts.add('plot', 'association_credible_set', association_credible_set_plot);\n}\n\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/../../../../../webpack/universalModuleDefinition","webpack://[name]/../../../../../webpack/bootstrap 069b89435249357eaca3","webpack://[name]/../../../../../src/app/stats.js","webpack://[name]/../../../../../src/app/gwas-credible-sets.js","webpack://[name]/../../../../../src/app/scoring.js","webpack://[name]/../../../../../src/app/marking.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-credible-sets.js"],"names":["factory","module","Object","prototype","hasOwnProperty","call","object","property","ninv","p","a","b","c","d","e","f","q","r","x","Math","abs","sqrt","log","rollup","scoring","stats","marking","_nlogp_to_z2","nlogp","pow","bayesFactors","nlogpvals","cap","Array","isArray","length","z2_2","map","item","capValue","max","exp","normalizeProbabilities","scores","sumValues","reduce","findCredibleSet","probs","cutoff","Number","isNaN","statsTotal","sortedStatsMap","index","sort","runningTotal","result","fill","i","value","score","markBoolean","credibleSetMembers","rescaleCredibleSet","sumMarkers","exports","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","definition","key","o","defineProperty","enumerable","get","obj","prop","install","LocusZoom","BaseUMAdapter","Adapters","add","config","super","arguments","this","_config","assign","threshold","significance_threshold","_prefix_namespace","state","credible_set_threshold","chr","start","end","join","options","assoc_data","base","_buildRequestOptions","_assoc_data","Promise","resolve","assoc_logp_name","_findPrefixedKey","some","val","posteriorProbabilities","credibleSet","credSetScaled","credSetBool","_provider_name","console","error","association_credible_set_tooltip","l","Layouts","html","closable","show","or","hide","and","association_credible_set_layer","id","namespace","data_operations","type","from","name","requires","params","fill_opacity","tooltip","match","send","receive","color","unshift","field","scale_function","parameters","field_value","then","annotation_credible_set_layer","id_field","x_axis","filters","operator","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip_positioning","annotation_credible_set","title","text","style","min_height","height","margin","top","right","bottom","left","inner_border","toolbar","axes","extent","render","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","data_layers","association_credible_set_panel","widgets","push","position","button_html","button_title","layer_name","default_config_display_name","display_name","display","point_shape","point_size","else","legend","shape","size","label","class","breaks","values","association_credible_set_plot","width","responsive_resize","min_region_scale","max_region_scale","panels","use"],"mappings":";gDAAA,IAAiDA,IASxC,WACT,O,YCTA,SAGA,cAGA,QACA,oBAGA,YACA,IACA,KACA,YAUA,OANA,mCAGA,OAGA,UAqCA,OAhCA,MAGA,MAGA,oBACA,UACA,2BACA,gBACA,cACA,SAMA,gBACA,sBACA,WAA4B,OAAOC,EAAgB,SACnD,WAAkC,OAAOA,GAEzC,OADA,aACA,GAIA,kBAAuD,OAAOC,OAAOC,UAAUC,eAAeC,KAAKC,EAAQC,IAG3G,OAGA,S;;;;;AC/CA,SAASC,EAAKC,GACV,IAIMC,EAAI,CACN,mBACA,mBACA,mBACA,kBACA,kBACA,iBACA,kBACA,oBAGEC,EAAI,CACN,kBACA,kBACA,kBACA,mBACA,kBACA,mBACA,mBAGEC,EAAI,CACN,mBACA,kBACA,kBACA,mBACA,mBACA,kBACA,oBACA,sBAGEC,EAAI,CACN,kBACA,mBACA,eACA,mBACA,oBACA,qBACA,uBAGEC,EAAI,CACN,kBACA,kBACA,mBACA,mBACA,oBACA,qBACA,sBACA,uBAGEC,EAAI,CACN,iBACA,kBACA,oBACA,qBACA,sBACA,qBACA,uBAGEC,EAAIP,EAAI,GACVQ,SAAGC,SAEP,GAAIC,KAAKC,IAAIJ,GAtEE,KAwEX,OAAOA,SAAYN,EAAE,IADrBO,EArEW,QAqEED,EAAIA,GACaN,EAAE,IAAOO,EAAIP,EAAE,IAAOO,EAAIP,EAAE,IAAOO,EAC3DP,EAAE,IAAOO,EAAIP,EAAE,IAAOO,EAAIP,EAAE,IAAOO,EAAIP,EAAE,WACnCC,EAAE,GAAKM,EAAIN,EAAE,IAAOM,EAAIN,EAAE,IAAOM,EAAIN,EAAE,IAAOM,EAChDN,EAAE,IAAOM,EAAIN,EAAE,IAAOM,EAAIN,EAAE,IAAOM,EAAI,GAUjD,MANIA,EADAD,EAAI,EACAP,EAGA,EAAMA,GAGN,GAkBJ,KAAM,kBAOV,OArBQS,GAHJD,EAAIE,KAAKE,MAAMF,KAAKG,IAAIL,MArFjB,SAwFSL,EAAE,IADdK,GArFG,KAsFoBL,EAAE,IAAOK,EAAIL,EAAE,IAAOK,EAAIL,EAAE,IAAOK,EACpDL,EAAE,IAAOK,EAAIL,EAAE,IAAOK,EAAIL,EAAE,IAAOK,EAAIL,EAAE,WACnCC,EAAE,GAAKI,EAAIJ,EAAE,IAAOI,EAAIJ,EAAE,IAAOI,EAAIJ,EAAE,IAAOI,EAChDJ,EAAE,IAAOI,EAAIJ,EAAE,IAAOI,EAAIJ,EAAE,IAAOI,EAAI,UAIrCH,EAAE,IADdG,GA9FG,GA+FoBH,EAAE,IAAOG,EAAIH,EAAE,IAAOG,EAAIH,EAAE,IAAOG,EACpDH,EAAE,IAAOG,EAAIH,EAAE,IAAOG,EAAIH,EAAE,IAAOG,EAAIH,EAAE,WACnCC,EAAE,GAAKE,EAAIF,EAAE,IAAOE,EAAIF,EAAE,IAAOE,EAAIF,EAAE,IAAOE,EAChDF,EAAE,IAAOE,EAAIF,EAAE,IAAOE,EAAIF,EAAE,IAAOE,EAAI,GAOrDD,EAAI,IACJE,GAAKA,GAGFA,E,iDAKf,IAAMK,EAAS,CAAEf,Q,EAERA,O,UACMe,G,iHC9Hf,I,IAAA,M,IACA,M,IACA,M,qDAQSC,Q,YAASC,M,YAAOC,Q,uJCZzB,W;;;;uMAgBA,SAASC,EAAaC,GAClB,IAAMnB,EAAIU,KAAKU,IAAI,IAAKD,GACxB,OAAIA,EAAQ,IAEDT,KAAKU,KAAI,IAAArB,MAAKC,EAAI,GAAI,GAMrB,iBAAmBmB,EAAS,iBAY5C,SAASE,EAAaC,GAAqB,IAAVC,IAAU,yDACvC,IAAKC,MAAMC,QAAQH,KAAgBA,EAAUI,OACzC,KAAM,4CAMV,IAAIC,EAAOL,EAAUM,KAAI,SAAAC,GAAA,OAAQX,EAAaW,GAAQ,KAKtD,GAAIN,EAAK,CACL,IAAMO,EAAWpB,KAAKqB,IAAL,MAAArB,KAAA,EAAYiB,IAAQ,IACjCG,EAAW,IACXH,EAAOA,EAAKC,KAAI,SAAAC,GAAA,OAASA,EAAOC,MAGxC,OAAOH,EAAKC,KAAI,SAAAC,GAAA,OAAQnB,KAAKsB,IAAIH,MAUrC,SAASI,EAAuBC,GAC5B,IAAMC,EAAYD,EAAOE,QAAO,SAACnC,EAAGC,GAAJ,OAAUD,EAAIC,IAAG,GACjD,OAAOgC,EAAON,KAAI,SAAAC,GAAA,OAAQA,EAAOM,KAGrC,IAAMrB,EAAS,CAAEO,eAAcY,0B,UAChBnB,E,EACNO,e,EAAcY,yB,EAGdf,gB;;;;GClET,SAASmB,EAAgBC,GAAoB,IAAbC,EAAa,uDAAN,IAEnC,IAAKf,MAAMC,QAAQa,KAAWA,EAAMZ,OAChC,KAAM,kCAEV,GAAwB,iBAAXa,GAAyBA,EAAS,GAAKA,EAAS,GAAOC,OAAOC,MAAMF,GAC7E,KAAM,0CAGV,IAAMG,EAAaJ,EAAMF,QAAO,SAACnC,EAAGC,GAAJ,OAAUD,EAAIC,IAAG,GACjD,GAAIwC,GAAc,EACd,KAAM,4CAUV,IANA,IAAMC,EAAiBL,EAClBV,KAAI,SAACC,EAAMe,GAAP,MAAiB,CAACf,EAAMe,MAC5BC,MAAK,SAAC5C,EAAGC,GAAJ,OAAWA,EAAE,GAAKD,EAAE,MAE1B6C,EAAe,EACbC,EAAS,IAAIvB,MAAMmB,EAAejB,QAAQsB,KAAK,GAC5CC,EAAI,EAAGA,EAAIN,EAAejB,OAAQuB,IAAK,SACvBN,EAAeM,GADQ,GACvCC,EADuC,KAChCN,EADgC,KAE5C,KAAIE,EAAeP,GAOf,MAJA,IAAMY,EAAQD,EAAQR,EACtBK,EAAOH,GAASO,EAChBL,GAAgBK,EAKxB,OAAOJ,EAcX,SAASK,EAAYC,GACjB,OAAOA,EAAmBzB,KAAI,SAAAC,GAAA,QAAUA,KAiB5C,SAASyB,EAAmBD,GACxB,IAAME,EAAaF,EAAmBjB,QAAO,SAACnC,EAAGC,GAAJ,OAAUD,EAAIC,IAAG,GAC9D,OAAOmD,EAAmBzB,KAAI,SAAAC,GAAA,OAAQA,EAAO0B,KAGjD,IAAMzC,EAAS,CAAEuB,kBAAiBe,cAAaE,sB,UAChCxC,E,EACNuB,kB,EAAiBe,c,EAAaE,yBLtFrC9D,EAAOgE,QAAUjE,MMDfkE,EAA2B,GAG/B,SAASC,EAAoBC,GAE5B,GAAGF,EAAyBE,GAC3B,OAAOF,EAAyBE,GAAUH,QAG3C,IAAIhE,EAASiE,EAAyBE,GAAY,CAGjDH,QAAS,IAOV,OAHAI,EAAoBD,GAAU/D,KAAKJ,EAAOgE,QAAShE,EAAQA,EAAOgE,QAASE,GAGpElE,EAAOgE,QCnBfE,EAAoBG,EAAKrE,IACxB,IAAIsE,EAAStE,GAAUA,EAAOuE,WAC7B,IAAOvE,EAAiB,QACxB,IAAM,EAEP,OADAkE,EAAoBtD,EAAE0D,EAAQ,CAAE7D,EAAG6D,IAC5BA,GCLRJ,EAAoBtD,EAAI,CAACoD,EAASQ,KACjC,IAAI,IAAIC,KAAOD,EACXN,EAAoBQ,EAAEF,EAAYC,KAASP,EAAoBQ,EAAEV,EAASS,IAC5ExE,OAAO0E,eAAeX,EAASS,EAAK,CAAEG,YAAY,EAAMC,IAAKL,EAAWC,MCJ3EP,EAAoBQ,EAAI,CAACI,EAAKC,IAAU9E,OAAOC,UAAUC,eAAeC,KAAK0E,EAAKC,G,gECkClF,SAASC,EAASC,GACd,MAAMC,EAAgBD,EAAUE,SAASN,IAAI,iBAoF7CI,EAAUE,SAASC,IAAI,gBA3EvB,cAA4BF,EASxB,YAAYG,GACRC,SAASC,WAETC,KAAKC,QAAUxF,OAAOyF,OAClB,CAAEC,UAAW,IAAMC,uBAAwB,OAC3CJ,KAAKC,SAETD,KAAKK,mBAAoB,EAG7B,aAAcC,GAEV,MAAO,CADWA,EAAMC,wBAA0BP,KAAKC,QAAQE,UAC5CG,EAAME,IAAKF,EAAMG,MAAOH,EAAMI,KAAKC,KAAK,KAG/D,qBAAqBC,EAASC,GAC1B,MAAMC,EAAOhB,MAAMiB,wBAAwBhB,WAE3C,OADAe,EAAKE,YAAcH,EACZC,EAGX,gBAAgBF,GACZ,MAAM,YAACI,GAAeJ,EACtB,IAAKI,EAAYtE,OAEb,OAAOuE,QAAQC,QAAQ,IAG3B,MAAMC,EAAkBnB,KAAKoB,iBAAiBJ,EAAY,GAAI,cAExDb,EAAYH,KAAKC,QAAQE,UAGzB7D,EAAY0E,EAAYpE,KAAKC,GAASA,EAAKsE,KAEjD,IAAK7E,EAAU+E,MAAMC,GAAQA,GAAOtB,KAAKC,QAAQG,yBAG7C,OAAOa,QAAQC,QAAQF,GAG3B,IACI,MAAM9D,EAAS,EAAAnB,QAAA,aAAqBO,GAC9BiF,EAAyB,EAAAxF,QAAA,uBAA+BmB,GAIxDsE,EAAc,EAAAvF,QAAA,gBAAwBsF,EAAwBpB,GAC9DsB,EAAgB,EAAAxF,QAAA,mBAA2BuF,GAC3CE,EAAc,EAAAzF,QAAA,YAAoBuF,GAIxC,IAAK,IAAIvD,EAAI,EAAGA,EAAI+C,EAAYtE,OAAQuB,IACpC+C,EAAY/C,GAAG,GAAG2C,EAAQe,iCAAmCJ,EAAuBtD,GACpF+C,EAAY/C,GAAG,GAAG2C,EAAQe,mCAAqCF,EAAcxD,GAC7E+C,EAAY/C,GAAG,GAAG2C,EAAQe,4BAA8BD,EAAYzD,GAE1E,MAAO5C,GAELuG,QAAQC,MAAMxG,GAElB,OAAO4F,QAAQC,QAAQF,MAa/B,MAAMc,EAAmC,WAErC,MAAMC,EAAItC,EAAUuC,QAAQ3C,IAAI,UAAW,wBAE3C,OADA0C,EAAEE,MAAQ,qIACHF,EAJ8B,GAOzCtC,EAAUuC,QAAQpC,IAAI,UAAW,2BAA4BkC,GAgB7DrC,EAAUuC,QAAQpC,IAAI,UAAW,0BARO,CACpCsC,UAAU,EACVC,KAAM,CAAEC,GAAI,CAAC,cAAe,aAC5BC,KAAM,CAAEC,IAAK,CAAC,gBAAiB,eAC/BL,KAAM,sQAaV,MAAMM,EAAiC,WACnC,MAAMzB,EAAOrB,EAAUuC,QAAQ3C,IAAI,aAAc,sBAAuB,CACpEmD,GAAI,yBACJC,UAAW,CAAE,MAAS,QAAS,QAAW,UAAW,GAAM,MAC3DC,gBAAiB,CACb,CACIC,KAAM,QACNC,KAAM,CAAC,QAAS,YAAa,mBAEjC,CACID,KAAM,aACNE,KAAM,kBACNC,SAAU,CAAC,UAAW,MACtBC,OAAQ,CAAC,iBAAkB,kBAGnCC,aAAc,GACdC,QAASxD,EAAUuC,QAAQ3C,IAAI,UAAW,4BAC1C6D,MAAO,CAAEC,KAAM,gBAAiBC,QAAS,mBAU7C,OARAtC,EAAKuC,MAAMC,QAAQ,CACfC,MAAO,cACPC,eAAgB,KAChBC,WAAY,CACRC,aAAa,EACbC,KAAM,aAGP7C,EA5B4B,GA8BvCrB,EAAUuC,QAAQpC,IAAI,aAAc,2BAA4B2C,GAQhE,MAAMqB,EAAgC,CAClCnB,UAAW,CAAE,MAAS,QAAS,QAAW,WAC1CC,gBAAiB,CAAC,CACdC,KAAM,QACNC,KAAM,CAAC,QAAS,oBAEpBJ,GAAI,wBACJG,KAAM,mBACNkB,SAAU,gBACVC,OAAQ,CACJP,MAAO,kBAEXF,MAAO,CACH,CACIE,MAAO,cACPC,eAAgB,KAChBC,WAAY,CACRC,aAAa,EACbC,KAAM,YAGd,WAEJT,MAAO,CAAEC,KAAM,gBAAiBC,QAAS,iBACzCW,QAAS,CAEL,CAAER,MAAO,oBAAqBS,SAAU,IAAK9F,OAAO,IAExD+F,UAAW,CACPC,YAAa,CACT,CAAEC,OAAQ,MAAOC,OAAQ,gBAE7BC,WAAY,CACR,CAAEF,OAAQ,QAASC,OAAQ,gBAE/BE,QAAS,CACL,CAAEH,OAAQ,SAAUC,OAAQ,WAAYG,WAAW,IAEvDC,aAAc,CACV,CAAEL,OAAQ,SAAUC,OAAQ,cAGpCnB,QAASxD,EAAUuC,QAAQ3C,IAAI,UAAW,2BAC1CoF,oBAAqB,OAEzBhF,EAAUuC,QAAQpC,IAAI,aAAc,0BAA2BgE,GAQ/D,MAAMc,EAA0B,CAC5BlC,GAAI,wBACJmC,MAAO,CAAEC,KAAM,2BAA4BnJ,EAAG,GAAIoJ,MAAO,CAAE,YAAa,SACxEC,WAAY,GACZC,OAAQ,GACRC,OAAQ,CAAEC,IAAK,GAAIC,MAAO,GAAIC,OAAQ,GAAIC,KAAM,IAChDC,aAAc,qBACdC,QAAS7F,EAAUuC,QAAQ3C,IAAI,UAAW,kBAC1CkG,KAAM,CACF9J,EAAG,CAAE+J,OAAQ,QAASC,QAAQ,IAElCC,YAAa,CACTC,wBAAwB,EACxBC,gBAAgB,EAChBC,UAAU,GAEdC,YAAa,CACTrG,EAAUuC,QAAQ3C,IAAI,aAAc,6BAG5CI,EAAUuC,QAAQpC,IAAI,QAAS,0BAA2B8E,GAQ1D,MAAMqB,EAAiC,WACnC,MAAMhE,EAAItC,EAAUuC,QAAQ3C,IAAI,QAAS,cAAe,CACpDmD,GAAI,0BACJsD,YAAa,CACTrG,EAAUuC,QAAQ3C,IAAI,aAAc,gBACpCI,EAAUuC,QAAQ3C,IAAI,aAAc,eACpCI,EAAUuC,QAAQ3C,IAAI,aAAc,+BAqG5C,OAjGA0C,EAAEuD,QAAQU,QAAQC,KACd,CACItD,KAAM,kBACNuD,SAAU,QACV7C,MAAO,OAEP8C,YAAa,qBACbC,aAAc,uCACdC,WAAY,yBACZC,4BAA6B,mCAE7B1F,QAAS,CACL,CAEI2F,aAAc,6BACdC,QAAS,CACLC,YAAa,SACbC,WAAY,GACZrD,MAAO,CACHE,MAAO,oBACPC,eAAgB,KAChBC,WAAY,CACRC,aAAa,EACbC,KAAM,UACNgD,KAAM,YAGdC,OAAQ,CACJ,CACIC,MAAO,SACPxD,MAAO,UACPyD,KAAM,GACNC,MAAO,kBACPC,MAAO,yBAEX,CACIH,MAAO,SACPxD,MAAO,UACPyD,KAAM,GACNC,MAAO,sBACPC,MAAO,4BAKvB,CAEIT,aAAc,8CACdC,QAAS,CACLC,YAAa,SACbC,WAAY,GACZrD,MAAO,CACH,CACIE,MAAO,2BACPC,eAAgB,KAChBC,WAAY,CACRC,YAAa,EACbC,KAAM,YAGd,CACIH,eAAgB,cAChBD,MAAO,2BACPE,WAAY,CACRwD,OAAQ,CAAC,EAAG,GACZC,OAAQ,CAAC,UAAW,cAIhCN,OAAQ,CACJ,CACIC,MAAO,SACPxD,MAAO,UACPyD,KAAM,GACNC,MAAO,kBACPC,MAAO,yBAEX,CACIH,MAAO,SACPxD,MAAO,UACPyD,KAAM,GACNC,MAAO,oBACPC,MAAO,yBAEX,CACIH,MAAO,SACPxD,MAAO,UACPyD,KAAM,GACNC,MAAO,oBACPC,MAAO,+BAQ5BjF,EA3G4B,GA6GvCtC,EAAUuC,QAAQpC,IAAI,QAAS,2BAA4BmG,GAQ3D,MAAMoB,EAAgC,CAClC7G,MAAO,GACP8G,MAAO,IACPrC,OAAQ,IACRsC,mBAAmB,EACnBC,iBAAkB,IAClBC,iBAAkB,IAClBjC,QAAS7F,EAAUuC,QAAQ3C,IAAI,UAAW,wBAC1CmI,OAAQ,CACJ/H,EAAUuC,QAAQ3C,IAAI,QAAS,4BAC/BI,EAAUuC,QAAQ3C,IAAI,QAAS,2BAC/BI,EAAUuC,QAAQ3C,IAAI,QAAS,WAGvCI,EAAUuC,QAAQpC,IAAI,OAAQ,2BAA4BuH,GAIrC,oBAAd1H,WAGPA,UAAUgI,IAAIjI,GAIlB,W","file":"ext/lz-credible-sets.min.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"gwasCredibleSets\"] = factory();\n\telse\n\t\troot[\"gwasCredibleSets\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 069b89435249357eaca3","/** \n * @module stats \n * @license MIT\n * */\n\n/**\n * The inverse of the standard normal CDF. May be used to determine the Z-score for the desired quantile.\n *\n * This is an implementation of algorithm AS241:\n * https://www.jstor.org/stable/2347330\n * \n * @param {number} p The desired quantile of the standard normal distribution.\n * @returns {number}\n */\nfunction ninv(p) {\n const SPLIT1 = 0.425;\n const SPLIT2 = 5.0;\n const CONST1 = 0.180625;\n const CONST2 = 1.6;\n const a = [\n 3.3871328727963666080E0,\n 1.3314166789178437745E2,\n 1.9715909503065514427E3,\n 1.3731693765509461125E4,\n 4.5921953931549871457E4,\n 6.7265770927008700853E4,\n 3.3430575583588128105E4,\n 2.5090809287301226727E3\n ];\n\n const b = [\n 4.2313330701600911252E1,\n 6.8718700749205790830E2,\n 5.3941960214247511077E3,\n 2.1213794301586595867E4,\n 3.9307895800092710610E4,\n 2.8729085735721942674E4,\n 5.2264952788528545610E3\n ];\n\n const c = [\n 1.42343711074968357734E0,\n 4.63033784615654529590E0,\n 5.76949722146069140550E0,\n 3.64784832476320460504E0,\n 1.27045825245236838258E0,\n 2.41780725177450611770E-1,\n 2.27238449892691845833E-2,\n 7.74545014278341407640E-4\n ];\n\n const d = [\n 2.05319162663775882187E0,\n 1.67638483018380384940E0,\n 6.89767334985100004550E-1,\n 1.48103976427480074590E-1,\n 1.51986665636164571966E-2,\n 5.47593808499534494600E-4,\n 1.05075007164441684324E-9\n ];\n\n const e = [\n 6.65790464350110377720E0,\n 5.46378491116411436990E0,\n 1.78482653991729133580E0,\n 2.96560571828504891230E-1,\n 2.65321895265761230930E-2,\n 1.24266094738807843860E-3,\n 2.71155556874348757815E-5,\n 2.01033439929228813265E-7\n ];\n\n const f = [\n 5.99832206555887937690E-1,\n 1.36929880922735805310E-1,\n 1.48753612908506148525E-2,\n 7.86869131145613259100E-4,\n 1.84631831751005468180E-5,\n 1.42151175831644588870E-7,\n 2.04426310338993978564E-15\n ];\n\n const q = p - 0.5;\n let r, x;\n\n if (Math.abs(q) < SPLIT1) {\n r = CONST1 - q * q;\n return q * ((((((( a[7] * r + a[6] ) * r + a[5] ) * r + a[4] ) * r\n + a[3] ) * r + a[2] ) * r + a[1] ) * r + a[0] ) /\n ((((((( b[6] * r + b[5] ) * r + b[4] ) * r + b[3] ) * r\n + b[2] ) * r + b[1] ) * r + b[0] ) * r + 1.0 );\n }\n else {\n if (q < 0) {\n r = p\n }\n else {\n r = 1.0 - p\n }\n\n if (r > 0) {\n r = Math.sqrt(-Math.log(r));\n if (r <= SPLIT2) {\n r -= CONST2;\n x = ((((((( c[7] * r + c[6] ) * r + c[5] ) * r + c[4] ) * r\n + c[3] ) * r + c[2] ) * r + c[1] ) * r + c[0] ) /\n ((((((( d[6] * r + d[5] ) * r + d[4] ) * r + d[3] ) * r\n + d[2] ) * r + d[1] ) * r + d[0] ) * r + 1.0 );\n }\n else {\n r -= SPLIT2;\n x = ((((((( e[7] * r + e[6] ) * r + e[5] ) * r + e[4] ) * r\n + e[3] ) * r + e[2] ) * r + e[1] ) * r + e[0] ) /\n ((((((( f[6] * r + f[5] ) * r + f[4] ) * r + f[3] ) * r\n + f[2] ) * r + f[1] ) * r + f[0] ) * r + 1.0 );\n }\n }\n else {\n throw('Not implemented')\n }\n\n if (q < 0) {\n x = -x\n }\n\n return x;\n }\n}\n\n// Hack: A single global object representing the contents of the module\nconst rollup = { ninv };\n\nexport { ninv };\nexport default rollup;\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/stats.js","/** \n * Functions for calculating credible sets and Bayes factors from\n * genome-wide association study (GWAS) results. \n * @module gwas-credible-sets \n * @license MIT\n */\n\nimport stats from './stats';\nimport scoring from './scoring';\nimport marking from './marking';\n\n// HACK: Because a primary audience is targets that do not have any module system, we will expose submodules from the\n// top-level module. (by representing each sub-module as a \"rollup object\" that exposes its internal methods)\n// Then, submodules may be accessed as `window.gwasCredibleSets.stats`, etc\n\n// If you are using a real module system, please import from sub-modules directly- these global helpers are a bit of\n// a hack and may go away in the future\nexport { scoring, stats, marking };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/gwas-credible-sets.js","/** \n * @module scoring \n * @license MIT\n */\n\nimport { ninv } from './stats';\n\n\n/**\n * Convert a -log10 p-value to Z^2.\n *\n * Very large -log10 p-values (very small p-values) cannot be converted to a Z-statistic directly in the browser due to\n * limitations in javascript (64-bit floats.) These values are handled using an approximation:\n * for small p-values, Z_i^2 has a linear relationship with -log10 p-value.\n *\n * The approximation begins for -log10 p-values >= 300.\n *\n * @param nlogp\n * @return {number}\n * @private\n */\nfunction _nlogp_to_z2(nlogp) {\n const p = Math.pow(10, -nlogp);\n if (nlogp < 300) {\n // Use exact method when within the range of 64-bit floats (approx 10^-323)\n return Math.pow(ninv(p / 2), 2);\n }\n else {\n // For very small p-values, -log10(pval) and Z^2 have a linear relationship\n // This avoids issues with needing higher precision floats when doing the calculation\n // with ninv\n return (4.59884133027944 * nlogp) - 5.88085867031722\n }\n}\n\n/**\n * Calculate a Bayes factor exp(Z^2 / 2) based on p-values. If the Z-score is very large, the Bayes factors\n * are calculated in an inexact (capped) manner that makes the calculation tractable but preserves comparisons.\n * @param {Number[]} nlogpvals An array of -log10(p-value) entries\n * @param {Boolean} [cap=true] Whether to apply an inexact method. If false, some values in the return array may\n * be represented as \"Infinity\", but the Bayes factors will be directly calculated wherever possible.\n * @return {Number[]} An array of exp(Z^2 / 2) statistics\n */\nfunction bayesFactors(nlogpvals, cap=true) {\n if (!Array.isArray(nlogpvals) || ! nlogpvals.length) {\n throw 'Must provide a non-empty array of pvalues';\n }\n\n // 1. Convert the pvalues to Z^2 / 2 values. Divide by 2 before applying the cap, because it means fewer values will\n // need to be truncated. This does affect some of the raw bayes factors that are returned (when a cap is needed),\n // but the resulting credible set contents / posterior probabilities are unchanged.\n let z2_2 = nlogpvals.map(item => _nlogp_to_z2(item) / 2);\n\n // 2. Calculate bayes factor, using a truncation approach that prevents overrunning the max float64 value\n // (when Z^2 / 2 > 709 or so). As safeguard, we could (but currently don't) check that exp(Z^2 / 2) is not larger\n // than infinity.\n if (cap) {\n const capValue = Math.max(...z2_2) - 708; // The real cap is ~709; this should prevent any value from exceeding it\n if (capValue > 0) {\n z2_2 = z2_2.map(item => (item - capValue));\n }\n }\n return z2_2.map(item => Math.exp(item));\n}\n\n/**\n * Normalize so that sum of all elements = 1.0. This method must be applied to bayes factors before calculating any\n * credible set.\n *\n * @param {Number[]} scores An array of probability scores for all elements in the range\n * @returns {Number[]} Posterior probabilities\n */\nfunction normalizeProbabilities(scores) {\n const sumValues = scores.reduce((a, b) => a + b, 0);\n return scores.map(item => item / sumValues);\n}\n\nconst rollup = { bayesFactors, normalizeProbabilities };\nexport default rollup;\nexport { bayesFactors, normalizeProbabilities };\n\n// Export additional symbols for unit testing only (not part of public interface for the module)\nexport { _nlogp_to_z2 };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/scoring.js","/**\n * @module marking\n * @license MIT\n */\n\n/**\n * Given an array of probabilities, determine which elements of the array fall within the X% credible set,\n * where X is the cutoff value.\n *\n * @param {Number[]} probs Calculated probabilities used to rank the credible set. This method will normalize the\n * provided input to ensure that the values sum to 1.0.\n * @param {Number} [cutoff=0.95] Keep taking items until we have accounted for >= this fraction of the total probability.\n * For example, 0.95 would represent the 95% credible set.\n * @return {Number[]} An array with posterior probabilities (for the items in the credible set), and zero for all\n * other elements. This array is the same length as the provided probabilities array.\n */\nfunction findCredibleSet(probs, cutoff=0.95) {\n // Type checking\n if (!Array.isArray(probs) || !probs.length) {\n throw 'Probs must be a non-empty array';\n }\n if (!(typeof cutoff === 'number' ) || cutoff < 0 || cutoff > 1.0 || Number.isNaN(cutoff)) {\n throw 'Cutoff must be a number between 0 and 1';\n }\n\n const statsTotal = probs.reduce((a, b) => a + b, 0);\n if (statsTotal <= 0) {\n throw 'Sum of provided probabilities must be > 0';\n }\n\n // Sort the probabilities by largest first, while preserving a map to original item order\n const sortedStatsMap = probs\n .map((item, index) => [item, index])\n .sort((a, b) => (b[0] - a[0]));\n\n let runningTotal = 0;\n const result = new Array(sortedStatsMap.length).fill(0);\n for (let i = 0; i < sortedStatsMap.length; i++) {\n let [value, index] = sortedStatsMap[i];\n if (runningTotal < cutoff) {\n // Convert from a raw score to posterior probability by dividing the item under consideration\n // by sum of all probabilities.\n const score = value / statsTotal;\n result[index] = score;\n runningTotal += score;\n } else {\n break;\n }\n }\n return result;\n}\n\n/**\n * Given a numeric [pre-calculated credible set]{@link #findCredibleSet}, return an array of booleans where true\n * denotes membership in the credible set.\n *\n * This is a helper method used when visualizing the members of the credible set by raw membership.\n *\n * @param {Number[]} credibleSetMembers An array indicating contributions to the credible set, where non-members are\n * represented by some falsy value.\n * @return {Boolean[]} An array of booleans identifying whether or not each item is in the credible set.\n * This array is the same length as the provided credible set array.\n */\nfunction markBoolean(credibleSetMembers) {\n return credibleSetMembers.map(item => !!item);\n}\n\n/**\n * Visualization helper method for rescaling data to a predictable output range, eg when range for a color gradient\n * must be specified in advance.\n *\n * Given an array of probabilities for items in a credible set, rescale the probabilities within only the credible\n * set to their total sum.\n *\n * Example for 95% credible set: [0.92, 0.06, 0.02] -> [0.938, 0.061, 0]. The first two elements here\n * belong to the credible set, the last element does not.\n *\n * @param {Number[]} credibleSetMembers Calculated probabilities used to rank the credible set.\n * @return {Number[]} The fraction of credible set probabilities each item accounts for.\n * This array is the same length as the provided credible set.\n */\nfunction rescaleCredibleSet(credibleSetMembers) {\n const sumMarkers = credibleSetMembers.reduce((a, b) => a + b, 0);\n return credibleSetMembers.map(item => item / sumMarkers);\n}\n\nconst rollup = { findCredibleSet, markBoolean, rescaleCredibleSet };\nexport default rollup;\nexport { findCredibleSet, markBoolean, rescaleCredibleSet };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/marking.js","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Custom code used to power credible sets demonstration example. This is not part of the core LocusZoom library,\n * but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~CredibleSetLZ}\n * * {@link module:LocusZoom_Layouts~association_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_layer}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set_plot}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import credibleSets from 'locuszoom/esm/ext/lz-credible-sets';\n * LocusZoom.use(credibleSets);\n * ```\n @module\n*/\n\nimport {marking, scoring} from 'gwas-credible-sets';\n\nfunction install (LocusZoom) {\n const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter');\n\n /**\n * (**extension**) Custom data adapter that calculates the 95% credible set based on provided association data.\n * This source must be requested as the second step in a chain, after a previous step that returns fields required\n * for the calculation. (usually, it follows a request for GWAS summary statistics)\n * @alias module:LocusZoom_Adapters~CredibleSetLZ\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n class CredibleSetLZ extends BaseUMAdapter {\n /**\n * @param {Number} [config.params.threshold=0.95] The credible set threshold (eg 95%). Will continue selecting SNPs\n * until the posterior probabilities add up to at least this fraction of the total.\n * @param {Number} [config.params.significance_threshold=7.301] Do not perform a credible set calculation for this\n * region unless AT LEAST ONE SNP (as -log10p) exceeds the line of GWAS signficance. Otherwise we are declaring a\n * credible set when there is no evidence of anything being significant at all. If one snp is significant, it will\n * create a credible set for the entire region; the resulting set may include things below the line of significance.\n */\n constructor(config) {\n super(...arguments);\n // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p)\n this._config = Object.assign(\n { threshold: 0.95, significance_threshold: 7.301 },\n this._config\n );\n this._prefix_namespace = false;\n }\n\n _getCacheKey (state) {\n const threshold = state.credible_set_threshold || this._config.threshold;\n return [threshold, state.chr, state.start, state.end].join('_');\n }\n\n _buildRequestOptions(options, assoc_data) {\n const base = super._buildRequestOptions(...arguments);\n base._assoc_data = assoc_data;\n return base;\n }\n\n _performRequest(options) {\n const {_assoc_data} = options;\n if (!_assoc_data.length) {\n // No credible set can be calculated because there is no association data for this region\n return Promise.resolve([]);\n }\n\n const assoc_logp_name = this._findPrefixedKey(_assoc_data[0], 'log_pvalue');\n\n const threshold = this._config.threshold;\n\n // Calculate raw bayes factors and posterior probabilities based on information returned from the API\n const nlogpvals = _assoc_data.map((item) => item[assoc_logp_name]);\n\n if (!nlogpvals.some((val) => val >= this._config.significance_threshold)) {\n // If NO points have evidence of significance, define the credible set to be empty\n // (rather than make a credible set that we don't think is meaningful)\n return Promise.resolve(_assoc_data);\n }\n\n try {\n const scores = scoring.bayesFactors(nlogpvals);\n const posteriorProbabilities = scoring.normalizeProbabilities(scores);\n\n // Use scores to mark the credible set in various ways (depending on your visualization preferences,\n // some of these may not be needed)\n const credibleSet = marking.findCredibleSet(posteriorProbabilities, threshold);\n const credSetScaled = marking.rescaleCredibleSet(credibleSet);\n const credSetBool = marking.markBoolean(credibleSet);\n\n // Annotate each response record based on credible set membership. This has the effect of joining\n // credset results to assoc data directly within the adapter (no separate join needed)\n for (let i = 0; i < _assoc_data.length; i++) {\n _assoc_data[i][`${options._provider_name}:posterior_prob`] = posteriorProbabilities[i];\n _assoc_data[i][`${options._provider_name}:contrib_fraction`] = credSetScaled[i];\n _assoc_data[i][`${options._provider_name}:is_member`] = credSetBool[i];\n }\n } catch (e) {\n // If the calculation cannot be completed, return the data without annotation fields\n console.error(e);\n }\n return Promise.resolve(_assoc_data);\n }\n }\n\n LocusZoom.Adapters.add('CredibleSetLZ', CredibleSetLZ);\n\n // Add related layouts to the central global registry\n /**\n * (**extension**) Tooltip layout that appends credible set posterior probability to the default association tooltip (for SNPs in the credible set)\n * @alias module:LocusZoom_Layouts~association_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_tooltip = function () {\n // Extend a known tooltip with an extra row of info showing posterior probabilities\n const l = LocusZoom.Layouts.get('tooltip', 'standard_association');\n l.html += '{{#if credset:posterior_prob}}
Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}';\n return l;\n }();\n\n LocusZoom.Layouts.add('tooltip', 'association_credible_set', association_credible_set_tooltip);\n\n /**\n * (**extension**) A tooltip layout for annotation (rug) tracks that provides information about credible set members\n * @alias module:LocusZoom_Layouts~annotation_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{assoc:variant|htmlescape}}
'\n + 'P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
' +\n '{{#if credset:posterior_prob}}
Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}',\n };\n LocusZoom.Layouts.add('tooltip', 'annotation_credible_set', annotation_credible_set_tooltip);\n\n /**\n * (**extension**) A data layer layout that shows GWAS summary statistics overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n\n const association_credible_set_layer = function () {\n const base = LocusZoom.Layouts.get('data_layer', 'association_pvalues', {\n id: 'associationcredibleset',\n namespace: { 'assoc': 'assoc', 'credset': 'credset', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)', 'credset(assoc)'],\n },\n {\n type: 'left_match',\n name: 'credset_plus_ld',\n requires: ['credset', 'ld'], // The credible sets demo wasn't fully moved over to the new data operations system, and as such it is a bit weird\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n fill_opacity: 0.7,\n tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set'),\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n });\n base.color.unshift({\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#FFf000',\n },\n });\n return base;\n }();\n LocusZoom.Layouts.add('data_layer', 'association_credible_set', association_credible_set_layer);\n\n /**\n * (**extension**) A data layer layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_layer = {\n namespace: { 'assoc': 'assoc', 'credset': 'credset' },\n data_operations: [{\n type: 'fetch',\n from: ['assoc', 'credset(assoc)'],\n }],\n id: 'annotationcredibleset',\n type: 'annotation_track',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#001cee',\n },\n },\n '#00CC00',\n ],\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'credset:is_member', operator: '=', value: true },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: LocusZoom.Layouts.get('tooltip', 'annotation_credible_set'),\n tooltip_positioning: 'top',\n };\n LocusZoom.Layouts.add('data_layer', 'annotation_credible_set', annotation_credible_set_layer);\n\n /**\n * (**extension**) A panel layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set = {\n id: 'annotationcredibleset',\n title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } },\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 50, bottom: 10, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel'),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'annotation_credible_set'),\n ],\n };\n LocusZoom.Layouts.add('panel', 'annotation_credible_set', annotation_credible_set);\n\n /**\n * (**extension**) A panel layout that shows GWAS summary statistics in a standard LocusZoom view, overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_panel = function () {\n const l = LocusZoom.Layouts.get('panel', 'association', {\n id: 'associationcrediblesets',\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'significance'),\n LocusZoom.Layouts.get('data_layer', 'recomb_rate'),\n LocusZoom.Layouts.get('data_layer', 'association_credible_set'),\n ],\n });\n // Add \"display options\" button to control how credible set coloring is overlaid on the standard association plot\n l.toolbar.widgets.push(\n {\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n layer_name: 'associationcredibleset',\n default_config_display_name: 'Linkage Disequilibrium (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: '95% credible set (boolean)', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n point_shape: 'circle',\n point_size: 40,\n color: {\n field: 'credset:is_member',\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#00CC00',\n else: '#CCCCCC',\n },\n },\n legend: [ // Tells the legend how to represent this display option\n {\n shape: 'circle',\n color: '#00CC00',\n size: 40,\n label: 'In credible set',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#CCCCCC',\n size: 40,\n label: 'Not in credible set',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n {\n // Second option. The same plot- or even the same field- can be colored in more than one way.\n display_name: '95% credible set (gradient by contribution)',\n display: {\n point_shape: 'circle',\n point_size: 40,\n color: [\n {\n field: 'credset:contrib_fraction',\n scale_function: 'if',\n parameters: {\n field_value: 0,\n then: '#777777',\n },\n },\n {\n scale_function: 'interpolate',\n field: 'credset:contrib_fraction',\n parameters: {\n breaks: [0, 1],\n values: ['#fafe87', '#9c0000'],\n },\n },\n ],\n legend: [\n {\n shape: 'circle',\n color: '#777777',\n size: 40,\n label: 'No contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#fafe87',\n size: 40,\n label: 'Some contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#9c0000',\n size: 40,\n label: 'Most contribution',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n ],\n }\n );\n return l;\n }();\n LocusZoom.Layouts.add('panel', 'association_credible_set', association_credible_set_panel);\n\n /**\n * (**extension**) A standard LocusZoom plot layout, with additional credible set information.\n * @alias module:LocusZoom_Layouts~association_credible_set_plot\n * @type plot\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_plot = {\n state: {},\n width: 800,\n height: 450,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n LocusZoom.Layouts.get('panel', 'association_credible_set'),\n LocusZoom.Layouts.get('panel', 'annotation_credible_set'),\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n LocusZoom.Layouts.add('plot', 'association_credible_set', association_credible_set_plot);\n}\n\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-dynamic-urls.min.js b/dist/ext/lz-dynamic-urls.min.js index 5aad6d90..543361e4 100644 --- a/dist/ext/lz-dynamic-urls.min.js +++ b/dist/ext/lz-dynamic-urls.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.4 */ +/*! Locuszoom 0.14.0-beta.1 */ var LzDynamicUrls;(()=>{"use strict";var t={d:(n,e)=>{for(var o in e)t.o(e,o)&&!t.o(n,o)&&Object.defineProperty(n,o,{enumerable:!0,get:e[o]})},o:(t,n)=>Object.prototype.hasOwnProperty.call(t,n)},n={};function e(t){const n={};if(t){const e=("?"===t[0]?t.substr(1):t).split("&");for(let t=0;ta});const a={paramsFromUrl:s,extractValues:o,plotUpdatesUrl:function(t,n,o){o=o||r;const c=function(c){const r=e(window.location.search),s=o(t,n,c),a=Object.assign({},r,s);if(Object.keys(a).some((function(t){return r[t]!=a[t]}))){const t=(i=a,`?${Object.keys(i).map((function(t){return`${encodeURIComponent(t)}=${encodeURIComponent(i[t])}`})).join("&")}`);Object.keys(r).length?history.pushState({},document.title,t):history.replaceState({},document.title,t)}var i};return t.on("state_changed",c),c},plotWatchesUrl:function(t,n,e){e=e||c;const o=function(o){const c=s(n);e(t,c)};return window.addEventListener("popstate",o),t.trackExternalListener(window,"popstate",o),o}};LzDynamicUrls=n.default})(); //# sourceMappingURL=lz-dynamic-urls.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-forest-track.min.js b/dist/ext/lz-forest-track.min.js index 01e5e2a5..a140e611 100644 --- a/dist/ext/lz-forest-track.min.js +++ b/dist/ext/lz-forest-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.4 */ -var LzForestTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var i in a)t.o(a,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:a[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>s});const a=d3;function i(t){const e=t.DataLayers.get("BaseDataLayer"),i={point_size:40,point_shape:"square",color:"#888888",fill_opacity:1,y_axis:{axis:2},id_field:"id",confidence_intervals:{start_field:"ci_start",end_field:"ci_end"}};class s extends e{constructor(e){e=t.Layouts.merge(e,i),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),a=`y${this.layout.y_axis.axis}_scale`,i=this.parent[a](t.data[this.layout.y_axis.field]),s=this.resolveScalableParameter(this.layout.point_size,t.data),r=Math.sqrt(s/Math.PI);return{x_min:e-r,x_max:e+r,y_min:i-r,y_max:i+r}}render(){const t=this._applyFilters(),e=`y${this.layout.y_axis.axis}_scale`;if(this.layout.confidence_intervals&&this.layout.fields.includes(this.layout.confidence_intervals.start_field)&&this.layout.fields.includes(this.layout.confidence_intervals.end_field)){const a=this.svg.group.selectAll("rect.lz-data_layer-forest.lz-data_layer-forest-ci").data(t,(t=>t[this.layout.id_field])),i=t=>{let a=this.parent.x_scale(t[this.layout.confidence_intervals.start_field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`},s=t=>this.parent.x_scale(t[this.layout.confidence_intervals.end_field])-this.parent.x_scale(t[this.layout.confidence_intervals.start_field]),r=1;a.enter().append("rect").attr("class","lz-data_layer-forest lz-data_layer-forest-ci").attr("id",(t=>`${this.getElementId(t)}_ci`)).attr("transform",`translate(0, ${isNaN(this.parent.layout.height)?0:this.parent.layout.height})`).merge(a).attr("transform",i).attr("width",s).attr("height",r),a.exit().remove()}const i=this.svg.group.selectAll("path.lz-data_layer-forest.lz-data_layer-forest-point").data(t,(t=>t[this.layout.id_field])),s=isNaN(this.parent.layout.height)?0:this.parent.layout.height,r=a.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>{const i=this.resolveScalableParameter(this.layout.point_shape,t,e),s=`symbol${i.charAt(0).toUpperCase()+i.slice(1)}`;return a[s]||null}));i.enter().append("path").attr("class","lz-data_layer-forest lz-data_layer-forest-point").attr("id",(t=>this.getElementId(t))).attr("transform",`translate(0, ${s})`).merge(i).attr("transform",(t=>{let a=this.parent.x_scale(t[this.layout.x_axis.field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`})).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>{this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}}t.DataLayers.add("forest",s),t.DataLayers.add("category_forest",class extends s{_getDataExtent(t,e){const i=this.layout.confidence_intervals;if(i&&this.layout.fields.includes(i.start_field)&&this.layout.fields.includes(i.end_field)){const e=t=>+t[i.start_field],s=t=>+t[i.end_field];return[a.min(t,e),a.max(t,s)]}return super._getDataExtent(t,e)}getTicks(t,e){if(!["x","y1","y2"].includes(t))throw new Error(`Invalid dimension identifier ${t}`);if(t===`y${this.layout.y_axis.axis}`){const t=this.layout.y_axis.category_field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify category_field`);return this.data.map(((e,a)=>({y:a+1,text:e[t]})))}return[]}applyCustomDataMethods(){const t=this.layout.y_axis.field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify yaxis.field`);return this.data=this.data.map(((e,a)=>(e[t]=a+1,e))),this.layout.y_axis.floor=0,this.layout.y_axis.ceiling=this.data.length+1,this}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(i);const s=i;LzForestTrack=e.default})(); +/*! Locuszoom 0.14.0-beta.1 */ +var LzForestTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var i in a)t.o(a,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:a[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>s});const a=d3;function i(t){const e=t.DataLayers.get("BaseDataLayer"),i={point_size:40,point_shape:"square",color:"#888888",fill_opacity:1,y_axis:{axis:2},id_field:"id",confidence_intervals:{start_field:"ci_start",end_field:"ci_end"}};class s extends e{constructor(e){e=t.Layouts.merge(e,i),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),a=`y${this.layout.y_axis.axis}_scale`,i=this.parent[a](t.data[this.layout.y_axis.field]),s=this.resolveScalableParameter(this.layout.point_size,t.data),r=Math.sqrt(s/Math.PI);return{x_min:e-r,x_max:e+r,y_min:i-r,y_max:i+r}}render(){const t=this._applyFilters(),e=`y${this.layout.y_axis.axis}_scale`;if(this.layout.confidence_intervals&&this.layout.confidence_intervals.start_field&&this.layout.confidence_intervals.end_field){const a=this.svg.group.selectAll("rect.lz-data_layer-forest.lz-data_layer-forest-ci").data(t,(t=>t[this.layout.id_field])),i=t=>{let a=this.parent.x_scale(t[this.layout.confidence_intervals.start_field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`},s=t=>{const{start_field:e,end_field:a}=this.layout.confidence_intervals,i=this.parent.x_scale,s=i(t[a])-i(t[e]);return Math.max(s,1)},r=1;a.enter().append("rect").attr("class","lz-data_layer-forest lz-data_layer-forest-ci").attr("id",(t=>`${this.getElementId(t)}_ci`)).attr("transform",`translate(0, ${isNaN(this.parent.layout.height)?0:this.parent.layout.height})`).merge(a).attr("transform",i).attr("width",s).attr("height",r),a.exit().remove()}const i=this.svg.group.selectAll("path.lz-data_layer-forest.lz-data_layer-forest-point").data(t,(t=>t[this.layout.id_field])),s=isNaN(this.parent.layout.height)?0:this.parent.layout.height,r=a.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>{const i=this.resolveScalableParameter(this.layout.point_shape,t,e),s=`symbol${i.charAt(0).toUpperCase()+i.slice(1)}`;return a[s]||null}));i.enter().append("path").attr("class","lz-data_layer-forest lz-data_layer-forest-point").attr("id",(t=>this.getElementId(t))).attr("transform",`translate(0, ${s})`).merge(i).attr("transform",(t=>{let a=this.parent.x_scale(t[this.layout.x_axis.field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`})).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>{this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}}t.DataLayers.add("forest",s),t.DataLayers.add("category_forest",class extends s{_getDataExtent(t,e){const{confidence_intervals:i}=this.layout;if(i&&i.start_field&&i.end_field){const e=t=>+t[i.start_field],s=t=>+t[i.end_field];return[a.min(t,e),a.max(t,s)]}return super._getDataExtent(t,e)}getTicks(t,e){if(!["x","y1","y2"].includes(t))throw new Error(`Invalid dimension identifier ${t}`);if(t===`y${this.layout.y_axis.axis}`){const t=this.layout.y_axis.category_field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify category_field`);return this.data.map(((e,a)=>({y:a+1,text:e[t]})))}return[]}applyCustomDataMethods(){const t=this.layout.y_axis.field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify yaxis.field`);return this.data=this.data.map(((e,a)=>(e[t]=a+1,e))),this.layout.y_axis.floor=0,this.layout.y_axis.ceiling=this.data.length+1,this}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(i);const s=i;LzForestTrack=e.default})(); //# sourceMappingURL=lz-forest-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-forest-track.min.js.map b/dist/ext/lz-forest-track.min.js.map index f1907e8e..8d59dca6 100644 --- a/dist/ext/lz-forest-track.min.js.map +++ b/dist/ext/lz-forest-track.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/ext/lz-forest-track.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","install","LocusZoom","BaseDataLayer","DataLayers","default_layout","point_size","point_shape","color","fill_opacity","y_axis","axis","id_field","confidence_intervals","start_field","end_field","Forest","layout","Layouts","merge","super","arguments","tooltip","x_center","this","parent","x_scale","data","x_axis","field","y_scale","y_center","resolveScalableParameter","offset","Math","sqrt","PI","x_min","x_max","y_min","y_max","track_data","_applyFilters","fields","includes","ci_selection","svg","group","selectAll","d","ci_transform","x","y","isNaN","ci_width","ci_height","enter","append","attr","getElementId","height","exit","remove","points_selection","initial_y","shape","size","i","type","shape_name","factory_name","charAt","toUpperCase","slice","on","element_data","emit","applyBehaviors","bind","add","axis_config","ci_config","min","max","_getDataExtent","dimension","config","Error","category_field","id","map","item","index","text","field_to_add","floor","ceiling","length","use"],"mappings":"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","file":"ext/lz-forest-track.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Forest plot track, designed for use with PheWAS style datasets.\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_DataLayers~forest}\n * * {@link module:LocusZoom_DataLayers~category_forest}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import ForestTrack from 'locuszoom/esm/ext/lz-forest-track';\n * LocusZoom.use(ForestTrack);\n * ```\n *\n * Then use the layouts made available by this extension. (see demos and documentation for guidance)\n *\n * @module\n */\nimport * as d3 from 'd3';\n\n\nfunction install (LocusZoom) {\n const BaseDataLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n const default_layout = {\n point_size: 40,\n point_shape: 'square',\n color: '#888888',\n fill_opacity: 1,\n y_axis: {\n axis: 2,\n },\n id_field: 'id',\n confidence_intervals: {\n start_field: 'ci_start',\n end_field: 'ci_end',\n },\n };\n\n /**\n * (**extension**) Forest Data Layer\n * Implements a standard forest plot. In order to space out points, any layout using this must specify axis ticks\n * and extent in advance.\n *\n * If you are using dynamically fetched data, consider using `category_forest` instead.\n * @alias module:LocusZoom_DataLayers~forest\n * @see module:LocusZoom_DataLayers~BaseDataLayer\n * @see {@link module:ext/lz-forest-track} for required extension and installation instructions\n */\n class Forest extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='square'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of each point\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} layout.x_axis.field A field specifying the x-coordinate of the mark (eg square)\n * @param {string} layout.y_axis.field A field specifying the y-coordinate. Use `category_forest` if you just want to\n * lay out a series of forest markings in order without worrying about this.\n * @param [layout.confidence_intervals.start_field='ci_start'] The field that specifies the start of confidence interval\n * @param [layout.confidence_intervals.end_field='ci_end'] The field that specifies the start of confidence interval\n */\n constructor(layout) {\n layout = LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n }\n\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n return {\n x_min: x_center - offset,\n x_max: x_center + offset,\n y_min: y_center - offset,\n y_max: y_center + offset,\n };\n }\n\n /**\n * @fires event:element_clicked\n */\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n\n // Generate confidence interval paths if fields are defined\n if (this.layout.confidence_intervals\n && this.layout.fields.includes(this.layout.confidence_intervals.start_field)\n && this.layout.fields.includes(this.layout.confidence_intervals.end_field)) {\n // Generate a selection for all forest plot confidence intervals\n const ci_selection = this.svg.group\n .selectAll('rect.lz-data_layer-forest.lz-data_layer-forest-ci')\n .data(track_data, (d) => {\n return d[this.layout.id_field];\n });\n\n const ci_transform = (d) => {\n let x = this.parent[x_scale](d[this.layout.confidence_intervals.start_field]);\n let y = this.parent[y_scale](d[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n return `translate(${x}, ${y})`;\n };\n const ci_width = (d) => {\n return this.parent[x_scale](d[this.layout.confidence_intervals.end_field])\n - this.parent[x_scale](d[this.layout.confidence_intervals.start_field]);\n };\n const ci_height = 1;\n // Create confidence interval rect elements\n ci_selection.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-forest lz-data_layer-forest-ci')\n .attr('id', (d) => `${this.getElementId(d)}_ci`)\n .attr('transform', `translate(0, ${isNaN(this.parent.layout.height) ? 0 : this.parent.layout.height})`)\n .merge(ci_selection)\n .attr('transform', ci_transform)\n .attr('width', ci_width)\n .attr('height', ci_height);\n\n // Remove old elements as needed\n ci_selection.exit()\n .remove();\n }\n\n // Generate a selection for all forest plot points\n const points_selection = this.svg.group\n .selectAll('path.lz-data_layer-forest.lz-data_layer-forest-point')\n .data(track_data, (d) => {\n return d[this.layout.id_field];\n });\n\n // Create elements, apply class, ID, and initial position\n const initial_y = isNaN(this.parent.layout.height) ? 0 : this.parent.layout.height;\n\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => {\n let x = this.parent[x_scale](d[this.layout.x_axis.field]);\n let y = this.parent[y_scale](d[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n return `translate(${x}, ${y})`;\n };\n\n const fill = (d, i) => this.resolveScalableParameter(this.layout.color, d, i);\n const fill_opacity = (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i);\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => {\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const shape_name = this.resolveScalableParameter(this.layout.point_shape, d, i);\n const factory_name = `symbol${shape_name.charAt(0).toUpperCase() + shape_name.slice(1)}`;\n return d3[factory_name] || null;\n });\n\n points_selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-forest lz-data_layer-forest-point')\n .attr('id', (d) => this.getElementId(d))\n .attr('transform', `translate(0, ${initial_y})`)\n .merge(points_selection)\n .attr('transform', transform)\n .attr('fill', fill)\n .attr('fill-opacity', fill_opacity)\n .attr('d', shape);\n\n // Remove old elements as needed\n points_selection.exit()\n .remove();\n\n // Apply behaviors to points\n this.svg.group\n .on('click.event_emitter', (element_data) => {\n this.parent.emit('element_clicked', element_data, true);\n }).call(this.applyBehaviors.bind(this));\n }\n }\n\n /**\n * (**extension**) A y-aligned forest plot in which the y-axis represents item labels, which are dynamically\n * chosen when data is loaded. Each item is assumed to include both data and confidence intervals.\n * This allows generating forest plots without defining the layout in advance.\n * @alias module:LocusZoom_DataLayers~category_forest\n * @see module:LocusZoom_DataLayers~BaseDataLayer\n * @see {@link module:ext/lz-forest-track} for required extension and installation instructions\n */\n class CategoryForest extends Forest {\n _getDataExtent(data, axis_config) {\n // In a forest plot, the data range is determined by *three* fields (beta + CI start/end)\n const ci_config = this.layout.confidence_intervals;\n if (ci_config\n && this.layout.fields.includes(ci_config.start_field)\n && this.layout.fields.includes(ci_config.end_field)) {\n const min = (d) => +d[ci_config.start_field];\n const max = (d) => +d[ci_config.end_field];\n return [d3.min(data, min), d3.max(data, max)];\n }\n\n // If there are no confidence intervals set, then range must depend only on a single field\n return super._getDataExtent(data, axis_config);\n }\n\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n\n // Design assumption: one axis (y1 or y2) has the ticks, and the layout says which to use\n // Also assumes that every tick gets assigned a unique matching label\n const axis_num = this.layout.y_axis.axis;\n if (dimension === (`y${axis_num}`)) {\n const category_field = this.layout.y_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n\n return this.data.map((item, index) => ({ y: index + 1, text: item[category_field] }));\n } else {\n return [];\n }\n }\n\n applyCustomDataMethods () {\n // Add a synthetic yaxis field to ensure data is spread out on plot. Then, set axis floor and ceiling to\n // correct extents.\n const field_to_add = this.layout.y_axis.field;\n if (!field_to_add) {\n throw new Error(`Layout for ${this.layout.id} must specify yaxis.field`);\n }\n\n this.data = this.data.map((item, index) => {\n item[field_to_add] = index + 1;\n return item;\n });\n // Update axis extents based on one label for every point (with a bit of padding above and below)\n this.layout.y_axis.floor = 0;\n this.layout.y_axis.ceiling = this.data.length + 1;\n return this;\n }\n }\n\n LocusZoom.DataLayers.add('forest', Forest);\n LocusZoom.DataLayers.add('category_forest', CategoryForest);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/ext/lz-forest-track.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","install","LocusZoom","BaseDataLayer","DataLayers","default_layout","point_size","point_shape","color","fill_opacity","y_axis","axis","id_field","confidence_intervals","start_field","end_field","Forest","layout","Layouts","merge","super","arguments","tooltip","x_center","this","parent","x_scale","data","x_axis","field","y_scale","y_center","resolveScalableParameter","offset","Math","sqrt","PI","x_min","x_max","y_min","y_max","track_data","_applyFilters","ci_selection","svg","group","selectAll","d","ci_transform","x","y","isNaN","ci_width","scale","result","max","ci_height","enter","append","attr","getElementId","height","exit","remove","points_selection","initial_y","shape","size","i","type","shape_name","factory_name","charAt","toUpperCase","slice","on","element_data","emit","applyBehaviors","bind","add","axis_config","min","_getDataExtent","dimension","config","includes","Error","category_field","id","map","item","index","text","field_to_add","floor","ceiling","length","use"],"mappings":"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","file":"ext/lz-forest-track.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Forest plot track, designed for use with PheWAS style datasets.\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_DataLayers~forest}\n * * {@link module:LocusZoom_DataLayers~category_forest}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import ForestTrack from 'locuszoom/esm/ext/lz-forest-track';\n * LocusZoom.use(ForestTrack);\n * ```\n *\n * Then use the layouts made available by this extension. (see demos and documentation for guidance)\n *\n * @module\n */\nimport * as d3 from 'd3';\n\n\nfunction install (LocusZoom) {\n const BaseDataLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n const default_layout = {\n point_size: 40,\n point_shape: 'square',\n color: '#888888',\n fill_opacity: 1,\n y_axis: {\n axis: 2,\n },\n id_field: 'id',\n confidence_intervals: {\n start_field: 'ci_start',\n end_field: 'ci_end',\n },\n };\n\n /**\n * (**extension**) Forest Data Layer\n * Implements a standard forest plot. In order to space out points, any layout using this must specify axis ticks\n * and extent in advance.\n *\n * If you are using dynamically fetched data, consider using `category_forest` instead.\n * @alias module:LocusZoom_DataLayers~forest\n * @see module:LocusZoom_DataLayers~BaseDataLayer\n * @see {@link module:ext/lz-forest-track} for required extension and installation instructions\n */\n class Forest extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='square'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of each point\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} layout.x_axis.field A field specifying the x-coordinate of the mark (eg square)\n * @param {string} layout.y_axis.field A field specifying the y-coordinate. Use `category_forest` if you just want to\n * lay out a series of forest markings in order without worrying about this.\n * @param [layout.confidence_intervals.start_field='ci_start'] The field that specifies the start of confidence interval\n * @param [layout.confidence_intervals.end_field='ci_end'] The field that specifies the start of confidence interval\n */\n constructor(layout) {\n layout = LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n }\n\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n return {\n x_min: x_center - offset,\n x_max: x_center + offset,\n y_min: y_center - offset,\n y_max: y_center + offset,\n };\n }\n\n /**\n * @fires event:element_clicked\n */\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n\n // Generate confidence interval paths if fields are defined\n if (this.layout.confidence_intervals &&\n this.layout.confidence_intervals.start_field &&\n this.layout.confidence_intervals.end_field) {\n // Generate a selection for all forest plot confidence intervals\n const ci_selection = this.svg.group\n .selectAll('rect.lz-data_layer-forest.lz-data_layer-forest-ci')\n .data(track_data, (d) => {\n return d[this.layout.id_field];\n });\n\n const ci_transform = (d) => {\n let x = this.parent[x_scale](d[this.layout.confidence_intervals.start_field]);\n let y = this.parent[y_scale](d[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n return `translate(${x}, ${y})`;\n };\n const ci_width = (d) => {\n const {start_field, end_field} = this.layout.confidence_intervals;\n const scale = this.parent[x_scale];\n const result = scale(d[end_field]) - scale(d[start_field]);\n return Math.max(result, 1);\n };\n const ci_height = 1;\n // Create confidence interval rect elements\n ci_selection.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-forest lz-data_layer-forest-ci')\n .attr('id', (d) => `${this.getElementId(d)}_ci`)\n .attr('transform', `translate(0, ${isNaN(this.parent.layout.height) ? 0 : this.parent.layout.height})`)\n .merge(ci_selection)\n .attr('transform', ci_transform)\n .attr('width', ci_width) // Math.max(ci_width, 1))\n .attr('height', ci_height);\n\n // Remove old elements as needed\n ci_selection.exit()\n .remove();\n }\n\n // Generate a selection for all forest plot points\n const points_selection = this.svg.group\n .selectAll('path.lz-data_layer-forest.lz-data_layer-forest-point')\n .data(track_data, (d) => {\n return d[this.layout.id_field];\n });\n\n // Create elements, apply class, ID, and initial position\n const initial_y = isNaN(this.parent.layout.height) ? 0 : this.parent.layout.height;\n\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => {\n let x = this.parent[x_scale](d[this.layout.x_axis.field]);\n let y = this.parent[y_scale](d[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n return `translate(${x}, ${y})`;\n };\n\n const fill = (d, i) => this.resolveScalableParameter(this.layout.color, d, i);\n const fill_opacity = (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i);\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => {\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const shape_name = this.resolveScalableParameter(this.layout.point_shape, d, i);\n const factory_name = `symbol${shape_name.charAt(0).toUpperCase() + shape_name.slice(1)}`;\n return d3[factory_name] || null;\n });\n\n points_selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-forest lz-data_layer-forest-point')\n .attr('id', (d) => this.getElementId(d))\n .attr('transform', `translate(0, ${initial_y})`)\n .merge(points_selection)\n .attr('transform', transform)\n .attr('fill', fill)\n .attr('fill-opacity', fill_opacity)\n .attr('d', shape);\n\n // Remove old elements as needed\n points_selection.exit()\n .remove();\n\n // Apply behaviors to points\n this.svg.group\n .on('click.event_emitter', (element_data) => {\n this.parent.emit('element_clicked', element_data, true);\n }).call(this.applyBehaviors.bind(this));\n }\n }\n\n /**\n * (**extension**) A y-aligned forest plot in which the y-axis represents item labels, which are dynamically\n * chosen when data is loaded. Each item is assumed to include both data and confidence intervals.\n * This allows generating forest plots without defining the layout in advance.\n * @alias module:LocusZoom_DataLayers~category_forest\n * @see module:LocusZoom_DataLayers~BaseDataLayer\n * @see {@link module:ext/lz-forest-track} for required extension and installation instructions\n */\n class CategoryForest extends Forest {\n _getDataExtent(data, axis_config) {\n // In a forest plot, the data range is determined by *three* fields (beta + CI start/end)\n const { confidence_intervals } = this.layout;\n if (confidence_intervals && confidence_intervals.start_field && confidence_intervals.end_field) {\n const min = (d) => +d[confidence_intervals.start_field];\n const max = (d) => +d[confidence_intervals.end_field];\n return [d3.min(data, min), d3.max(data, max)];\n }\n\n // If there are no confidence intervals set, then range must depend only on a single field\n return super._getDataExtent(data, axis_config);\n }\n\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n\n // Design assumption: one axis (y1 or y2) has the ticks, and the layout says which to use\n // Also assumes that every tick gets assigned a unique matching label\n const axis_num = this.layout.y_axis.axis;\n if (dimension === (`y${axis_num}`)) {\n const category_field = this.layout.y_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n\n return this.data.map((item, index) => ({ y: index + 1, text: item[category_field] }));\n } else {\n return [];\n }\n }\n\n applyCustomDataMethods () {\n // Add a synthetic yaxis field to ensure data is spread out on plot. Then, set axis floor and ceiling to\n // correct extents.\n const field_to_add = this.layout.y_axis.field;\n if (!field_to_add) {\n throw new Error(`Layout for ${this.layout.id} must specify yaxis.field`);\n }\n\n this.data = this.data.map((item, index) => {\n item[field_to_add] = index + 1;\n return item;\n });\n // Update axis extents based on one label for every point (with a bit of padding above and below)\n this.layout.y_axis.floor = 0;\n this.layout.y_axis.ceiling = this.data.length + 1;\n return this;\n }\n }\n\n LocusZoom.DataLayers.add('forest', Forest);\n LocusZoom.DataLayers.add('category_forest', CategoryForest);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-intervals-enrichment.min.js b/dist/ext/lz-intervals-enrichment.min.js index 2a9e431a..f854c445 100644 --- a/dist/ext/lz-intervals-enrichment.min.js +++ b/dist/ext/lz-intervals-enrichment.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.4 */ -var LzIntervalsEnrichment;(()=>{"use strict";var e={d:(a,t)=>{for(var s in t)e.o(t,s)&&!e.o(a,s)&&Object.defineProperty(a,s,{enumerable:!0,get:t[s]})},o:(e,a)=>Object.prototype.hasOwnProperty.call(e,a)},a={};e.d(a,{default:()=>r});const t=Symbol.for("lzXCS"),s=Symbol.for("lzYCS"),n=Symbol.for("lzXCE"),i=Symbol.for("lzYCE");function l(e){const a={start_field:"start",end_field:"end",track_height:10,track_vertical_spacing:3,bounding_box_padding:2,color:"#B8B8B8",fill_opacity:.5,tooltip_positioning:"vertical"},l=e.DataLayers.get("BaseDataLayer");const r={namespace:{intervals:"intervals"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Tissue: {{{{namespace[intervals]}}tissueId|htmlescape}}
\n Range: {{{{namespace[intervals]}}chromosome|htmlescape}}: {{{{namespace[intervals]}}start|htmlescape}}-{{{{namespace[intervals]}}end|htmlescape}}
\n -log10 p: {{{{namespace[intervals]}}pValue|neglog10|scinotation|htmlescape}}
\n Enrichment (n-fold): {{{{namespace[intervals]}}fold|scinotation|htmlescape}}"},c={namespace:{intervals:"intervals"},id:"intervals_enrichment",type:"intervals_enrichment",tag:"intervals_enrichment",match:{send:"{{namespace[intervals]}}tissueId"},fields:["{{namespace[intervals]}}chromosome","{{namespace[intervals]}}start","{{namespace[intervals]}}end","{{namespace[intervals]}}pValue","{{namespace[intervals]}}fold","{{namespace[intervals]}}tissueId","{{namespace[intervals]}}ancestry"],id_field:"{{namespace[intervals]}}start",start_field:"{{namespace[intervals]}}start",end_field:"{{namespace[intervals]}}end",filters:[{field:"{{namespace[intervals]}}ancestry",operator:"=",value:"EU"},{field:"{{namespace[intervals]}}pValue",operator:"<=",value:.05},{field:"{{namespace[intervals]}}fold",operator:">",value:2}],y_axis:{axis:1,field:"{{namespace[intervals]}}fold",floor:0,upper_buffer:.1,min_extent:[0,10]},fill_opacity:.5,color:[{field:"{{namespace[intervals]}}tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:r},o={id:"interval_matches",type:"highlight_regions",namespace:{intervals:"intervals"},match:{receive:"{{namespace[intervals]}}tissueId"},fields:["{{namespace[intervals]}}start","{{namespace[intervals]}}end","{{namespace[intervals]}}tissueId","{{namespace[intervals]}}ancestry","{{namespace[intervals]}}pValue","{{namespace[intervals]}}fold"],start_field:"{{namespace[intervals]}}start",end_field:"{{namespace[intervals]}}end",merge_field:"{{namespace[intervals]}}tissueId",filters:[{field:"lz_is_match",operator:"=",value:!0},{field:"{{namespace[intervals]}}ancestry",operator:"=",value:"EU"},{field:"{{namespace[intervals]}}pValue",operator:"<=",value:.05},{field:"{{namespace[intervals]}}fold",operator:">",value:2}],color:[{field:"{{namespace[intervals]}}tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],fill_opacity:.1},d={id:"intervals_enrichment",tag:"intervals_enrichment",min_height:250,height:250,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"},y1:{label:"enrichment (n-fold)",label_offset:28}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[c]},p={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:e.Layouts.get("toolbar","standard_association",{unnamespaced:!0}),panels:[function(){const a=e.Layouts.get("panel","association",{unnamespaced:!0});return a.data_layers.unshift(o),a}(),d,e.Layouts.get("panel","genes")]};e.DataLayers.add("intervals_enrichment",class extends l{constructor(t){e.Layouts.merge(t,a),super(...arguments)}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}render(){const e=this._applyFilters(this.data),{start_field:a,end_field:l,bounding_box_padding:r,track_height:c}=this.layout,o=this.layout.y_axis.field,d=`y${this.layout.y_axis.axis}_scale`,{x_scale:p,[d]:m}=this.parent;e.forEach((e=>{e[t]=p(e[a]),e[n]=p(e[l]),e[s]=m(e[o])-this.getTrackHeight()/2+r,e[i]=e[s]+c})),e.sort(((e,a)=>{const s=e[n]-e[t];return a[n]-a[t]-s}));const f=this.svg.group.selectAll("rect").data(e);f.enter().append("rect").merge(f).attr("id",(e=>this.getElementId(e))).attr("x",(e=>e[t])).attr("y",(e=>e[s])).attr("width",(e=>e[n]-e[t])).attr("height",this.layout.track_height).attr("fill",((e,a)=>this.resolveScalableParameter(this.layout.color,e,a))).attr("fill-opacity",((e,a)=>this.resolveScalableParameter(this.layout.fill_opacity,e,a))),f.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this))}_getTooltipPosition(e){return{x_min:e.data[t],x_max:e.data[n],y_min:e.data[s],y_max:e.data[i]}}}),e.Layouts.add("tooltip","intervals_enrichment",r),e.Layouts.add("data_layer","intervals_enrichment",c),e.Layouts.add("panel","intervals_enrichment",d),e.Layouts.add("plot","intervals_association_enrichment",p)}"undefined"!=typeof LocusZoom&&LocusZoom.use(l);const r=l;LzIntervalsEnrichment=a.default})(); +/*! Locuszoom 0.14.0-beta.1 */ +var LzIntervalsEnrichment;(()=>{"use strict";var e={d:(t,a)=>{for(var i in a)e.o(a,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:a[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>n});const a=Symbol.for("lzXCS"),i=Symbol.for("lzYCS"),s=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function r(e){const t={start_field:"start",end_field:"end",track_height:10,track_vertical_spacing:3,bounding_box_padding:2,color:"#B8B8B8",fill_opacity:.5,tooltip_positioning:"vertical"},r=e.DataLayers.get("BaseDataLayer");const n={namespace:{intervals:"intervals"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Tissue: {{intervals:tissueId|htmlescape}}
\n Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
\n -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
\n Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}"},o={id:"intervals_enrichment",type:"intervals_enrichment",tag:"intervals_enrichment",namespace:{intervals:"intervals"},match:{send:"intervals:tissueId"},id_field:"intervals:start",start_field:"intervals:start",end_field:"intervals:end",filters:[{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],y_axis:{axis:1,field:"intervals:fold",floor:0,upper_buffer:.1,min_extent:[0,10]},fill_opacity:.5,color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:n},c={id:"interval_matches",type:"highlight_regions",namespace:{intervals:"intervals"},match:{receive:"intervals:tissueId"},start_field:"intervals:start",end_field:"intervals:end",merge_field:"intervals:tissueId",filters:[{field:"lz_is_match",operator:"=",value:!0},{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],fill_opacity:.1},d={id:"intervals_enrichment",tag:"intervals_enrichment",min_height:250,height:250,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"},y1:{label:"enrichment (n-fold)",label_offset:28}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[o]},h={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:e.Layouts.get("toolbar","standard_association"),panels:[function(){const t=e.Layouts.get("panel","association");return t.data_layers.unshift(c),t}(),d,e.Layouts.get("panel","genes")]};e.DataLayers.add("intervals_enrichment",class extends r{constructor(a){e.Layouts.merge(a,t),super(...arguments)}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}render(){const e=this._applyFilters(this.data),{start_field:t,end_field:r,bounding_box_padding:n,track_height:o}=this.layout,c=this.layout.y_axis.field,d=`y${this.layout.y_axis.axis}_scale`,{x_scale:h,[d]:f}=this.parent;e.forEach((e=>{e[a]=h(e[t]),e[s]=h(e[r]),e[i]=f(e[c])-this.getTrackHeight()/2+n,e[l]=e[i]+o})),e.sort(((e,t)=>{const i=e[s]-e[a];return t[s]-t[a]-i}));const _=this.svg.group.selectAll("rect").data(e);_.enter().append("rect").merge(_).attr("id",(e=>this.getElementId(e))).attr("x",(e=>e[a])).attr("y",(e=>e[i])).attr("width",(e=>e[s]-e[a])).attr("height",this.layout.track_height).attr("fill",((e,t)=>this.resolveScalableParameter(this.layout.color,e,t))).attr("fill-opacity",((e,t)=>this.resolveScalableParameter(this.layout.fill_opacity,e,t))),_.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this))}_getTooltipPosition(e){return{x_min:e.data[a],x_max:e.data[s],y_min:e.data[i],y_max:e.data[l]}}}),e.Layouts.add("tooltip","intervals_enrichment",n),e.Layouts.add("data_layer","intervals_enrichment",o),e.Layouts.add("panel","intervals_enrichment",d),e.Layouts.add("plot","intervals_association_enrichment",h)}"undefined"!=typeof LocusZoom&&LocusZoom.use(r);const n=r;LzIntervalsEnrichment=t.default})(); //# sourceMappingURL=lz-intervals-enrichment.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-enrichment.min.js.map b/dist/ext/lz-intervals-enrichment.min.js.map index f569fa57..1efdc478 100644 --- a/dist/ext/lz-intervals-enrichment.min.js.map +++ b/dist/ext/lz-intervals-enrichment.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-intervals-enrichment.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","XCS","Symbol","for","YCS","XCE","YCE","install","LocusZoom","default_layout","start_field","end_field","track_height","track_vertical_spacing","bounding_box_padding","color","fill_opacity","tooltip_positioning","BaseLayer","DataLayers","intervals_tooltip_layout","namespace","closable","show","or","hide","and","html","intervals_layer_layout","id","type","tag","match","send","fields","id_field","filters","field","operator","value","y_axis","axis","floor","upper_buffer","min_extent","scale_function","parameters","values","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip","intervals_highlight_layout","intervals","receive","merge_field","intervals_panel_layout","min_height","height","margin","top","right","bottom","left","inner_border","axes","x","label","label_offset","tick_format","extent","y1","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","scroll_to_zoom","x_linked","data_layers","intervals_plot_layout","state","width","responsive_resize","min_region_scale","max_region_scale","toolbar","Layouts","unnamespaced","panels","base","unshift","add","layout","merge","super","arguments","this","track_data","_applyFilters","data","y_field","y_axis_name","x_scale","y_scale","parent","forEach","item","getTrackHeight","sort","a","b","aspan","selection","svg","group","selectAll","enter","append","attr","d","getElementId","i","resolveScalableParameter","exit","remove","applyBehaviors","bind","x_min","x_max","y_min","y_max","use"],"mappings":"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","file":"ext/lz-intervals-enrichment.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Interval annotation track that groups annotations by enrichment value (a fixed y-axis) rather than by merged/split tracks.\n\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n\n * ### Features provided\n * * {@link module:LocusZoom_DataLayers~intervals_enrichment}\n * * {@link module:LocusZoom_Layouts~intervals_association_enrichment}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_panel}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_data_layer}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_tooltip}\n *\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```javascript\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n *\n * ```javascript\n * import LocusZoom from 'locuszoom';\n * import IntervalsTrack from 'locuszoom/esm/ext/lz-intervals-track';\n * LocusZoom.use(IntervalsTrack);\n * ```\n *\n * Then use the layouts made available by this extension. (see demos and documentation for guidance)\n * @module\n */\n\n// Coordinates (start, end) are cached to facilitate rendering\nconst XCS = Symbol.for('lzXCS');\nconst YCS = Symbol.for('lzYCS');\nconst XCE = Symbol.for('lzXCE');\nconst YCE = Symbol.for('lzYCE');\n\n\nfunction install(LocusZoom) {\n /**\n * @memberof module:LocusZoom_DataLayers~intervals_enrichment\n */\n const default_layout = {\n start_field: 'start',\n end_field: 'end',\n track_height: 10,\n track_vertical_spacing: 3,\n bounding_box_padding: 2,\n color: '#B8B8B8',\n fill_opacity: 0.5,\n tooltip_positioning: 'vertical',\n };\n\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n\n /**\n * Intervals-by-enrichment Data Layer\n *\n * Implements a data layer that groups interval annotations by enrichment value (a fixed y-axis)\n * @alias module:LocusZoom_DataLayers~intervals_enrichment\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\n class LzIntervalsEnrichment extends BaseLayer {\n /**\n * @param {string} [layout.start_field='start'] The field that defines interval start position\n * @param {string} [layout.end_field='end'] The field that defines interval end position\n * @param {number} [layout.track_height=10] The height of each interval rectangle, in px\n * @param {number} [layout.track_vertical_spacing=3]\n * @param {number} [layout.bounding_box_padding=2]\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#B8B8B8'] The color of each datum rectangle\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity of\n * each rectangle. The default is semi-transparent, because low-significance tracks may overlap very closely.\n * @param {string} [layout.tooltip_positioning='vertical']\n */\n constructor(layout) {\n LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n }\n\n // Helper function to sum layout values to derive total height for a single interval track\n getTrackHeight() {\n return this.layout.track_height\n + this.layout.track_vertical_spacing\n + (2 * this.layout.bounding_box_padding);\n }\n\n render() {\n // Determine the appropriate layout for tracks. Store the previous categories (y axis ticks) to decide\n // whether the axis needs to be re-rendered.\n\n // Apply filters to only render a specified set of points. Hidden fields will still be given space to render, but not shown.\n const track_data = this._applyFilters(this.data);\n\n const {start_field, end_field, bounding_box_padding, track_height} = this.layout;\n const y_field = this.layout.y_axis.field;\n const y_axis_name = `y${this.layout.y_axis.axis}_scale`;\n const { x_scale, [y_axis_name]: y_scale } = this.parent;\n\n // Calculate coordinates for each point\n track_data.forEach((item) => {\n item[XCS] = x_scale(item[start_field]);\n item[XCE] = x_scale(item[end_field]);\n item[YCS] = y_scale(item[y_field]) - this.getTrackHeight() / 2 + bounding_box_padding;\n item[YCE] = item[YCS] + track_height;\n });\n\n track_data.sort((a, b) => {\n // Simplistic layout algorithm that adds wide rectangles to the DOM first, so that small rectangles\n // in the same space are clickable (SVG element order determines z-index)\n const aspan = a[XCE] - a[XCS];\n const bspan = b[XCE] - b[XCS];\n return bspan - aspan;\n });\n\n const selection = this.svg.group.selectAll('rect')\n .data(track_data);\n\n selection.enter()\n .append('rect')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => d[XCS])\n .attr('y', (d) => d[YCS])\n .attr('width', (d) => d[XCE] - d[XCS])\n .attr('height', this.layout.track_height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n selection.exit()\n .remove();\n\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n return {\n x_min: tooltip.data[XCS],\n x_max: tooltip.data[XCE],\n y_min: tooltip.data[YCS],\n y_max: tooltip.data[YCE],\n };\n }\n }\n\n /**\n * (**extension**) A basic tooltip with information to be shown over an intervals-by-enrichment datum\n * @alias module:LocusZoom_Layouts~intervals_enrichment_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_tooltip_layout = {\n namespace: { 'intervals': 'intervals' },\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Tissue: {{{{namespace[intervals]}}tissueId|htmlescape}}
\n Range: {{{{namespace[intervals]}}chromosome|htmlescape}}: {{{{namespace[intervals]}}start|htmlescape}}-{{{{namespace[intervals]}}end|htmlescape}}
\n -log10 p: {{{{namespace[intervals]}}pValue|neglog10|scinotation|htmlescape}}
\n Enrichment (n-fold): {{{{namespace[intervals]}}fold|scinotation|htmlescape}}`,\n };\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals-by-enrichment display, in\n * which intervals are ranked by priority from enrichment analysis.\n *\n * @alias module:LocusZoom_Layouts~intervals_enrichment_data_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_layer_layout = {\n namespace: { 'intervals': 'intervals' },\n id: 'intervals_enrichment',\n type: 'intervals_enrichment',\n tag: 'intervals_enrichment',\n match: { send: '{{namespace[intervals]}}tissueId' },\n fields: ['{{namespace[intervals]}}chromosome', '{{namespace[intervals]}}start', '{{namespace[intervals]}}end', '{{namespace[intervals]}}pValue', '{{namespace[intervals]}}fold', '{{namespace[intervals]}}tissueId', '{{namespace[intervals]}}ancestry'],\n id_field: '{{namespace[intervals]}}start', // not a good ID field for overlapping intervals\n start_field: '{{namespace[intervals]}}start',\n end_field: '{{namespace[intervals]}}end',\n filters: [\n { field: '{{namespace[intervals]}}ancestry', operator: '=', value: 'EU' },\n { field: '{{namespace[intervals]}}pValue', operator: '<=', value: 0.05 },\n { field: '{{namespace[intervals]}}fold', operator: '>', value: 2.0 },\n ],\n y_axis: {\n axis: 1,\n field: '{{namespace[intervals]}}fold', // is this used for other than extent generation?\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n fill_opacity: 0.5, // Many intervals overlap: show all, even if the ones below can't be clicked\n color: [\n {\n field: '{{namespace[intervals]}}tissueId',\n scale_function: 'stable_choice',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'],\n },\n },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: intervals_tooltip_layout,\n };\n\n // This is tied to a rather specific demo, so it's not added to the reusable registry\n // Highlights areas of a scatter plot that match the HuGeAMP-provided enrichment analysis data\n // Relies on matching behavior/ interaction (not visible initially)\n const intervals_highlight_layout = {\n id: 'interval_matches',\n type: 'highlight_regions',\n namespace: { intervals: 'intervals' },\n match: { receive: '{{namespace[intervals]}}tissueId' },\n fields: ['{{namespace[intervals]}}start', '{{namespace[intervals]}}end', '{{namespace[intervals]}}tissueId', '{{namespace[intervals]}}ancestry', '{{namespace[intervals]}}pValue', '{{namespace[intervals]}}fold'],\n start_field: '{{namespace[intervals]}}start',\n end_field: '{{namespace[intervals]}}end',\n merge_field: '{{namespace[intervals]}}tissueId',\n filters: [\n { field: 'lz_is_match', operator: '=', value: true },\n { field: '{{namespace[intervals]}}ancestry', operator: '=', value: 'EU' },\n { field: '{{namespace[intervals]}}pValue', operator: '<=', value: 0.05 },\n { field: '{{namespace[intervals]}}fold', operator: '>', value: 2.0 },\n ],\n color: [{\n field: '{{namespace[intervals]}}tissueId',\n scale_function: 'stable_choice',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'],\n },\n }],\n fill_opacity: 0.1,\n };\n\n /**\n * (**extension**) A panel containing an intervals-by-enrichment data layer\n * @alias module:LocusZoom_Layouts~intervals_enrichment_panel\n * @type panel\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_panel_layout = {\n id: 'intervals_enrichment',\n tag: 'intervals_enrichment',\n min_height: 250,\n height: 250,\n margin: { top: 35, right: 50, bottom: 40, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'enrichment (n-fold)',\n label_offset: 28,\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [intervals_layer_layout],\n };\n\n /**\n * (**extension**) A plot layout that shows association summary statistics, genes, and intervals-by-enrichment data.\n * This layout provides interactive matching: clicking an interval marking causes area of the scatter plot to be\n * highlighted for any annotations that match the specified category.\n * It is intended to work with data in the HuGeAMP format.\n * @alias module:LocusZoom_Layouts~intervals_association_enrichment\n * @type plot\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_plot_layout = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association', { unnamespaced: true }),\n panels: [\n function () {\n const base = LocusZoom.Layouts.get('panel', 'association', { unnamespaced: true });\n base.data_layers.unshift(intervals_highlight_layout);\n return base;\n }(),\n intervals_panel_layout,\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n\n LocusZoom.DataLayers.add('intervals_enrichment', LzIntervalsEnrichment);\n\n LocusZoom.Layouts.add('tooltip', 'intervals_enrichment', intervals_tooltip_layout);\n LocusZoom.Layouts.add('data_layer', 'intervals_enrichment', intervals_layer_layout);\n LocusZoom.Layouts.add('panel', 'intervals_enrichment', intervals_panel_layout);\n LocusZoom.Layouts.add('plot', 'intervals_association_enrichment', intervals_plot_layout);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-intervals-enrichment.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","XCS","Symbol","for","YCS","XCE","YCE","install","LocusZoom","default_layout","start_field","end_field","track_height","track_vertical_spacing","bounding_box_padding","color","fill_opacity","tooltip_positioning","BaseLayer","DataLayers","intervals_tooltip_layout","namespace","closable","show","or","hide","and","html","intervals_layer_layout","id","type","tag","match","send","id_field","filters","field","operator","value","y_axis","axis","floor","upper_buffer","min_extent","scale_function","parameters","values","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip","intervals_highlight_layout","intervals","receive","merge_field","intervals_panel_layout","min_height","height","margin","top","right","bottom","left","inner_border","axes","x","label","label_offset","tick_format","extent","y1","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","scroll_to_zoom","x_linked","data_layers","intervals_plot_layout","state","width","responsive_resize","min_region_scale","max_region_scale","toolbar","Layouts","panels","base","unshift","add","layout","merge","super","arguments","this","track_data","_applyFilters","data","y_field","y_axis_name","x_scale","y_scale","parent","forEach","item","getTrackHeight","sort","a","b","aspan","selection","svg","group","selectAll","enter","append","attr","d","getElementId","i","resolveScalableParameter","exit","remove","applyBehaviors","bind","x_min","x_max","y_min","y_max","use"],"mappings":"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","file":"ext/lz-intervals-enrichment.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Interval annotation track that groups annotations by enrichment value (a fixed y-axis) rather than by merged/split tracks.\n\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n\n * ### Features provided\n * * {@link module:LocusZoom_DataLayers~intervals_enrichment}\n * * {@link module:LocusZoom_Layouts~intervals_association_enrichment}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_panel}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_data_layer}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_tooltip}\n *\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```javascript\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n *\n * ```javascript\n * import LocusZoom from 'locuszoom';\n * import IntervalsTrack from 'locuszoom/esm/ext/lz-intervals-track';\n * LocusZoom.use(IntervalsTrack);\n * ```\n *\n * Then use the layouts made available by this extension. (see demos and documentation for guidance)\n * @module\n */\n\n// Coordinates (start, end) are cached to facilitate rendering\nconst XCS = Symbol.for('lzXCS');\nconst YCS = Symbol.for('lzYCS');\nconst XCE = Symbol.for('lzXCE');\nconst YCE = Symbol.for('lzYCE');\n\n\nfunction install(LocusZoom) {\n /**\n * @memberof module:LocusZoom_DataLayers~intervals_enrichment\n */\n const default_layout = {\n start_field: 'start',\n end_field: 'end',\n track_height: 10,\n track_vertical_spacing: 3,\n bounding_box_padding: 2,\n color: '#B8B8B8',\n fill_opacity: 0.5,\n tooltip_positioning: 'vertical',\n };\n\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n\n /**\n * Intervals-by-enrichment Data Layer\n *\n * Implements a data layer that groups interval annotations by enrichment value (a fixed y-axis)\n * @alias module:LocusZoom_DataLayers~intervals_enrichment\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\n class LzIntervalsEnrichment extends BaseLayer {\n /**\n * @param {string} [layout.start_field='start'] The field that defines interval start position\n * @param {string} [layout.end_field='end'] The field that defines interval end position\n * @param {number} [layout.track_height=10] The height of each interval rectangle, in px\n * @param {number} [layout.track_vertical_spacing=3]\n * @param {number} [layout.bounding_box_padding=2]\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#B8B8B8'] The color of each datum rectangle\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity of\n * each rectangle. The default is semi-transparent, because low-significance tracks may overlap very closely.\n * @param {string} [layout.tooltip_positioning='vertical']\n */\n constructor(layout) {\n LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n }\n\n // Helper function to sum layout values to derive total height for a single interval track\n getTrackHeight() {\n return this.layout.track_height\n + this.layout.track_vertical_spacing\n + (2 * this.layout.bounding_box_padding);\n }\n\n render() {\n // Determine the appropriate layout for tracks. Store the previous categories (y axis ticks) to decide\n // whether the axis needs to be re-rendered.\n\n // Apply filters to only render a specified set of points. Hidden fields will still be given space to render, but not shown.\n const track_data = this._applyFilters(this.data);\n\n const {start_field, end_field, bounding_box_padding, track_height} = this.layout;\n const y_field = this.layout.y_axis.field;\n const y_axis_name = `y${this.layout.y_axis.axis}_scale`;\n const { x_scale, [y_axis_name]: y_scale } = this.parent;\n\n // Calculate coordinates for each point\n track_data.forEach((item) => {\n item[XCS] = x_scale(item[start_field]);\n item[XCE] = x_scale(item[end_field]);\n item[YCS] = y_scale(item[y_field]) - this.getTrackHeight() / 2 + bounding_box_padding;\n item[YCE] = item[YCS] + track_height;\n });\n\n track_data.sort((a, b) => {\n // Simplistic layout algorithm that adds wide rectangles to the DOM first, so that small rectangles\n // in the same space are clickable (SVG element order determines z-index)\n const aspan = a[XCE] - a[XCS];\n const bspan = b[XCE] - b[XCS];\n return bspan - aspan;\n });\n\n const selection = this.svg.group.selectAll('rect')\n .data(track_data);\n\n selection.enter()\n .append('rect')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => d[XCS])\n .attr('y', (d) => d[YCS])\n .attr('width', (d) => d[XCE] - d[XCS])\n .attr('height', this.layout.track_height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n selection.exit()\n .remove();\n\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n return {\n x_min: tooltip.data[XCS],\n x_max: tooltip.data[XCE],\n y_min: tooltip.data[YCS],\n y_max: tooltip.data[YCE],\n };\n }\n }\n\n /**\n * (**extension**) A basic tooltip with information to be shown over an intervals-by-enrichment datum\n * @alias module:LocusZoom_Layouts~intervals_enrichment_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_tooltip_layout = {\n namespace: { 'intervals': 'intervals' },\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Tissue: {{intervals:tissueId|htmlescape}}
\n Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
\n -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
\n Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}`,\n };\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals-by-enrichment display, in\n * which intervals are ranked by priority from enrichment analysis.\n *\n * @alias module:LocusZoom_Layouts~intervals_enrichment_data_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_layer_layout = {\n id: 'intervals_enrichment',\n type: 'intervals_enrichment',\n tag: 'intervals_enrichment',\n namespace: { 'intervals': 'intervals' },\n match: { send: 'intervals:tissueId' },\n id_field: 'intervals:start', // not a good ID field for overlapping intervals\n start_field: 'intervals:start',\n end_field: 'intervals:end',\n filters: [\n { field: 'intervals:ancestry', operator: '=', value: 'EU' },\n { field: 'intervals:pValue', operator: '<=', value: 0.05 },\n { field: 'intervals:fold', operator: '>', value: 2.0 },\n ],\n y_axis: {\n axis: 1,\n field: 'intervals:fold', // is this used for other than extent generation?\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n fill_opacity: 0.5, // Many intervals overlap: show all, even if the ones below can't be clicked\n color: [\n {\n field: 'intervals:tissueId',\n scale_function: 'stable_choice',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'],\n },\n },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: intervals_tooltip_layout,\n };\n\n // This is tied to a rather specific demo, so it's not added to the reusable registry\n // Highlights areas of a scatter plot that match the HuGeAMP-provided enrichment analysis data\n // Relies on matching behavior/ interaction (not visible initially)\n const intervals_highlight_layout = {\n id: 'interval_matches',\n type: 'highlight_regions',\n namespace: { intervals: 'intervals' },\n match: { receive: 'intervals:tissueId' },\n start_field: 'intervals:start',\n end_field: 'intervals:end',\n merge_field: 'intervals:tissueId',\n filters: [\n { field: 'lz_is_match', operator: '=', value: true },\n { field: 'intervals:ancestry', operator: '=', value: 'EU' },\n { field: 'intervals:pValue', operator: '<=', value: 0.05 },\n { field: 'intervals:fold', operator: '>', value: 2.0 },\n ],\n color: [{\n field: 'intervals:tissueId',\n scale_function: 'stable_choice',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'],\n },\n }],\n fill_opacity: 0.1,\n };\n\n /**\n * (**extension**) A panel containing an intervals-by-enrichment data layer\n * @alias module:LocusZoom_Layouts~intervals_enrichment_panel\n * @type panel\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_panel_layout = {\n id: 'intervals_enrichment',\n tag: 'intervals_enrichment',\n min_height: 250,\n height: 250,\n margin: { top: 35, right: 50, bottom: 40, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'enrichment (n-fold)',\n label_offset: 28,\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [intervals_layer_layout],\n };\n\n /**\n * (**extension**) A plot layout that shows association summary statistics, genes, and intervals-by-enrichment data.\n * This layout provides interactive matching: clicking an interval marking causes area of the scatter plot to be\n * highlighted for any annotations that match the specified category.\n * It is intended to work with data in the HuGeAMP format.\n * @alias module:LocusZoom_Layouts~intervals_association_enrichment\n * @type plot\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_plot_layout = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n function () {\n const base = LocusZoom.Layouts.get('panel', 'association');\n base.data_layers.unshift(intervals_highlight_layout);\n return base;\n }(),\n intervals_panel_layout,\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n\n LocusZoom.DataLayers.add('intervals_enrichment', LzIntervalsEnrichment);\n\n LocusZoom.Layouts.add('tooltip', 'intervals_enrichment', intervals_tooltip_layout);\n LocusZoom.Layouts.add('data_layer', 'intervals_enrichment', intervals_layer_layout);\n LocusZoom.Layouts.add('panel', 'intervals_enrichment', intervals_panel_layout);\n LocusZoom.Layouts.add('plot', 'intervals_association_enrichment', intervals_plot_layout);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-intervals-track.min.js b/dist/ext/lz-intervals-track.min.js index bf224e6a..4e676179 100644 --- a/dist/ext/lz-intervals-track.min.js +++ b/dist/ext/lz-intervals-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.4 */ -var LzIntervalsTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var s in a)t.o(a,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:a[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>o});const a=d3,s=Symbol.for("lzXCS"),r=Symbol.for("lzYCS"),i=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function n(t){const e=t.Adapters.get("BaseApiAdapter"),n=t.Widgets.get("_Button"),o=t.Widgets.get("BaseWidget");const c={start_field:"start",end_field:"end",track_label_field:"state_name",track_split_field:"state_id",track_split_order:"DESC",track_split_legend_to_y_axis:2,split_tracks:!0,track_height:15,track_vertical_spacing:3,bounding_box_padding:2,always_hide_legend:!1,color:"#B8B8B8",fill_opacity:1,tooltip_positioning:"vertical"},d=t.DataLayers.get("BaseDataLayer");const g={namespace:{intervals:"intervals"},closable:!1,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"{{{{namespace[intervals]}}state_name|htmlescape}}
{{{{namespace[intervals]}}start|htmlescape}}-{{{{namespace[intervals]}}end|htmlescape}}"},h={namespace:{intervals:"intervals"},id:"intervals",type:"intervals",tag:"intervals",fields:["{{namespace[intervals]}}start","{{namespace[intervals]}}end","{{namespace[intervals]}}state_id","{{namespace[intervals]}}state_name","{{namespace[intervals]}}itemRgb"],id_field:"{{namespace[intervals]}}start",start_field:"{{namespace[intervals]}}start",end_field:"{{namespace[intervals]}}end",track_split_field:"{{namespace[intervals]}}state_name",track_label_field:"{{namespace[intervals]}}state_name",split_tracks:!1,always_hide_legend:!0,color:[{field:"{{namespace[intervals]}}itemRgb",scale_function:"to_rgb"},{field:"{{namespace[intervals]}}state_name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],legend:[],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:g},_={id:"intervals",tag:"intervals",min_height:50,height:50,margin:{top:25,right:150,bottom:5,left:50},toolbar:function(){const e=t.Layouts.get("toolbar","standard_panel",{unnamespaced:!0});return e.widgets.push({type:"toggle_split_tracks",data_layer_id:"intervals",position:"right"}),e}(),axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},legend:{hidden:!0,orientation:"horizontal",origin:{x:50,y:0},pad_from_bottom:5},data_layers:[h]},u={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association",{unnamespaced:!0}),panels:[t.Layouts.get("panel","association"),t.Layouts.merge({unnamespaced:!0,min_height:120,height:120},_),t.Layouts.get("panel","genes")]};t.Adapters.add("IntervalLZ",class extends e{getURL(t,e,a){const s=`?filter=id in ${e.header.bedtracksource||this.params.source} and chromosome eq '${t.chr}' and start le ${t.end} and end ge ${t.start}`;return`${this.url}${s}`}}),t.DataLayers.add("intervals",class extends d{constructor(e){t.Layouts.merge(e,c),super(...arguments),this._previous_categories=[],this._categories=[]}initialize(){super.initialize(),this._statusnodes_group=this.svg.group.append("g").attr("class","lz-data-layer-intervals lz-data-layer-intervals-statusnode"),this._datanodes_group=this.svg.group.append("g").attr("class","lz-data_layer-intervals")}_arrangeTrackSplit(t){const{track_split_field:e}=this.layout,a={};return t.forEach((t=>{const s=t[e];Object.prototype.hasOwnProperty.call(a,s)||(a[s]=[]),a[s].push(t)})),a}_arrangeTracksLinear(t,e=!0){if(e)return[t];const{start_field:a,end_field:s}=this.layout,r=[[]];return t.forEach(((t,e)=>{for(let e=0;e{d[t].forEach((t=>{t[s]=e(t[a]),t[i]=e(t[n]),t[r]=g*this.getTrackHeight()+o,t[l]=t[r]+c,t.track=g}))})),[g,Object.values(d).reduce(((t,e)=>t.concat(e)),[])]}getElementStatusNodeId(t){if(this.layout.split_tracks){const e="object"==typeof t?t.track:t;return`${this.getBaseId()}-statusnode-${e}`.replace(/[^\w]/g,"_")}return null}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}_applyLayoutOptions(){const t=this,e=this._base_layout,a=this.layout,s=e.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function})),r=a.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function}));if(!s)throw new Error("Interval tracks must define a `categorical_bin` color scale");const i=s.parameters.categories.length&&s.parameters.values.length,l=e.legend&&e.legend.length;if(!!i^!!l)throw new Error("To use a manually specified color scheme, both color and legend options must be set.");const n=e.color.find((function(t){return t.scale_function&&"to_rgb"===t.scale_function})),o=n&&n.field,c=this._generateCategoriesFromData(this.data,o);if(!i&&!l){const e=this._makeColorScheme(c);r.parameters.categories=c.map((function(t){return t[0]})),r.parameters.values=e,this.layout.legend=c.map((function(e,a){const s=e[0],i={shape:"rect",width:9,label:e[1],color:r.parameters.values[a]};return i[t.layout.track_split_field]=s,i}))}}render(){this._applyLayoutOptions(),this._previous_categories=this._categories;const[t,e]=this._assignTracks(this.data);this._categories=t;if(!t.every(((t,e)=>t===this._previous_categories[e])))return void this.updateSplitTrackAxis(t);const l=this._applyFilters(e);this._statusnodes_group.selectAll("rect").remove();const n=this._statusnodes_group.selectAll("rect").data(a.range(t.length));if(this.layout.split_tracks){const t=this.getTrackHeight();n.enter().append("rect").attr("class","lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared").attr("rx",this.layout.bounding_box_padding).attr("ry",this.layout.bounding_box_padding).merge(n).attr("id",(t=>this.getElementStatusNodeId(t))).attr("x",0).attr("y",(e=>e*t)).attr("width",this.parent.layout.cliparea.width).attr("height",t-this.layout.track_vertical_spacing)}n.exit().remove();const o=this._datanodes_group.selectAll("rect").data(l,(t=>t[this.layout.id_field]));o.enter().append("rect").merge(o).attr("id",(t=>this.getElementId(t))).attr("x",(t=>t[s])).attr("y",(t=>t[r])).attr("width",(t=>t[i]-t[s])).attr("height",this.layout.track_height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),o.exit().remove(),this._datanodes_group.call(this.applyBehaviors.bind(this)),this.parent&&this.parent.legend&&this.parent.legend.render()}_getTooltipPosition(t){return{x_min:t.data[s],x_max:t.data[i],y_min:t.data[r],y_max:t.data[l]}}updateSplitTrackAxis(t){const e=!!this.layout.track_split_legend_to_y_axis&&`y${this.layout.track_split_legend_to_y_axis}`;if(this.layout.split_tracks){const a=+t.length||0,s=+this.layout.track_height||0,r=2*(+this.layout.bounding_box_padding||0)+(+this.layout.track_vertical_spacing||0),i=a*s+(a-1)*r;this.parent.scaleHeightToData(i),e&&this.parent.legend&&(this.parent.legend.hide(),this.parent.layout.axes[e]={render:!0,ticks:[],range:{start:i-this.layout.track_height/2,end:this.layout.track_height/2}},this.layout.legend.forEach((s=>{const r=s[this.layout.track_split_field];let i=t.findIndex((t=>t===r));-1!==i&&("DESC"===this.layout.track_split_order&&(i=Math.abs(i-a-1)),this.parent.layout.axes[e].ticks.push({y:i-1,text:s.label}))})),this.layout.y_axis={axis:this.layout.track_split_legend_to_y_axis,floor:1,ceiling:a}),this.parent_plot.positionPanels()}else e&&this.parent.legend&&(this.layout.always_hide_legend||this.parent.legend.show(),this.parent.layout.axes[e]={render:!1},this.parent.render());return this}toggleSplitTracks(){return this.layout.split_tracks=!this.layout.split_tracks,this.parent.legend&&!this.layout.always_hide_legend&&(this.parent.layout.margin.bottom=5+(this.layout.split_tracks?0:this.parent.legend.layout.height+5)),this.render(),this}_makeColorScheme(t){if(t.find((t=>t[2])))return t.map((t=>t[2]));const e=t.length;return e<=15?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(233,150,122)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(50,205,50)","rgb(255,69,0)","rgb(255,0,0)"]:e<=18?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]:["rgb(212,212,212)","rgb(128,128,128)","rgb(112,48,160)","rgb(230,184,183)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,102)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,150,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]}_generateCategoriesFromData(t,e){const a=this,s=this._base_layout.legend;if(s&&s.length)return s.map((t=>[t[this.layout.track_split_field],t.label,t.color]));const r={},i=[];return t.forEach((t=>{const s=t[a.layout.track_split_field];Object.prototype.hasOwnProperty.call(r,s)||(r[s]=null,i.push([s,t[this.layout.track_label_field],t[e]]))})),i}}),t.Layouts.add("tooltip","standard_intervals",g),t.Layouts.add("data_layer","intervals",h),t.Layouts.add("panel","intervals",_),t.Layouts.add("plot","interval_association",u),t.ScaleFunctions.add("to_rgb",(function(t,e){return e?`rgb(${e})`:null})),t.Widgets.add("toggle_split_tracks",class extends o{constructor(t){if(super(...arguments),t.data_layer_id||(t.data_layer_id="intervals"),!this.parent_panel.data_layers[t.data_layer_id])throw new Error("Toggle split tracks widget specifies an invalid data layer ID")}update(){const t=this.parent_panel.data_layers[this.layout.data_layer_id],e=t.layout.split_tracks?"Merge Tracks":"Split Tracks";return this.button?(this.button.setHtml(e),this.button.show(),this.parent.position(),this):(this.button=new n(this).setColor(this.layout.color).setHtml(e).setTitle("Toggle whether tracks are split apart or merged together").setOnclick((()=>{t.toggleSplitTracks(),this.scale_timeout&&clearTimeout(this.scale_timeout),this.scale_timeout=setTimeout((()=>{this.parent_panel.scaleHeightToData(),this.parent_plot.positionPanels()}),0),this.update()})),this.update())}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const o=n;LzIntervalsTrack=e.default})(); +/*! Locuszoom 0.14.0-beta.1 */ +var LzIntervalsTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var r in a)t.o(a,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>o});const a=d3,r=Symbol.for("lzXCS"),s=Symbol.for("lzYCS"),i=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function n(t){const e=t.Adapters.get("BaseUMAdapter"),n=t.Widgets.get("_Button"),o=t.Widgets.get("BaseWidget");function c(t,e){return e?`rgb(${e})`:null}const d={start_field:"start",end_field:"end",track_label_field:"state_name",track_split_field:"state_id",track_split_order:"DESC",track_split_legend_to_y_axis:2,split_tracks:!0,track_height:15,track_vertical_spacing:3,bounding_box_padding:2,always_hide_legend:!1,color:"#B8B8B8",fill_opacity:1,tooltip_positioning:"vertical"},g=t.DataLayers.get("BaseDataLayer");const _={closable:!1,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"{{intervals:state_name|htmlescape}}
{{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}"},h={namespace:{intervals:"intervals"},id:"intervals",type:"intervals",tag:"intervals",id_field:"{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}",start_field:"intervals:start",end_field:"intervals:end",track_split_field:"intervals:state_name",track_label_field:"intervals:state_name",split_tracks:!1,always_hide_legend:!0,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:state_name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],legend:[],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:_},u=t.Layouts.merge({id_field:"{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}",start_field:"intervals:chromStart",end_field:"intervals:chromEnd",track_split_field:"intervals:name",track_label_field:"intervals:name",split_tracks:!0,always_hide_legend:!1,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],tooltip:t.Layouts.merge({html:"Group: {{intervals:name|htmlescape}}
\nRegion: {{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}}\n{{#if intervals:score}}
\nScore: {{intervals:score|htmlescape}}{{/if}}"},_)},h),p={id:"intervals",tag:"intervals",min_height:50,height:50,margin:{top:25,right:150,bottom:5,left:50},toolbar:function(){const e=t.Layouts.get("toolbar","standard_panel");return e.widgets.push({type:"toggle_split_tracks",data_layer_id:"intervals",position:"right"}),e}(),axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},legend:{hidden:!0,orientation:"horizontal",origin:{x:50,y:0},pad_from_bottom:5},data_layers:[h]},y=t.Layouts.merge({min_height:120,height:120,data_layers:[u]},p),b={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association"),panels:[t.Layouts.get("panel","association"),t.Layouts.merge({min_height:120,height:120},p),t.Layouts.get("panel","genes")]};t.Adapters.add("IntervalLZ",class extends e{_getURL(t){const e=`?filter=id in ${this._config.source} and chromosome eq '${t.chr}' and start le ${t.end} and end ge ${t.start}`;return`${super._getURL(t)}${e}`}}),t.DataLayers.add("intervals",class extends g{constructor(e){t.Layouts.merge(e,d),super(...arguments),this._previous_categories=[],this._categories=[]}initialize(){super.initialize(),this._statusnodes_group=this.svg.group.append("g").attr("class","lz-data-layer-intervals lz-data-layer-intervals-statusnode"),this._datanodes_group=this.svg.group.append("g").attr("class","lz-data_layer-intervals")}_arrangeTrackSplit(t){const{track_split_field:e}=this.layout,a={};return t.forEach((t=>{const r=t[e];Object.prototype.hasOwnProperty.call(a,r)||(a[r]=[]),a[r].push(t)})),a}_arrangeTracksLinear(t,e=!0){if(e)return[t];const{start_field:a,end_field:r}=this.layout,s=[[]];return t.forEach(((t,e)=>{for(let e=0;e{d[t].forEach((t=>{t[r]=e(t[a]),t[i]=e(t[n]),t[s]=g*this.getTrackHeight()+o,t[l]=t[s]+c,t.track=g}))})),[g,Object.values(d).reduce(((t,e)=>t.concat(e)),[])]}getElementStatusNodeId(t){if(this.layout.split_tracks){const e="object"==typeof t?t.track:t;return`${this.getBaseId()}-statusnode-${e}`.replace(/[^\w]/g,"_")}return null}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}_applyLayoutOptions(){const t=this,e=this._base_layout,a=this.layout,r=e.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function})),s=a.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function}));if(!r)throw new Error("Interval tracks must define a `categorical_bin` color scale");const i=r.parameters.categories.length&&r.parameters.values.length,l=e.legend&&e.legend.length;if(!!i^!!l)throw new Error("To use a manually specified color scheme, both color and legend options must be set.");const n=e.color.find((function(t){return t.scale_function&&"to_rgb"===t.scale_function})),o=n&&n.field,c=this._generateCategoriesFromData(this.data,o);if(!i&&!l){const e=this._makeColorScheme(c);s.parameters.categories=c.map((function(t){return t[0]})),s.parameters.values=e,this.layout.legend=c.map((function(e,a){const r=e[0],i={shape:"rect",width:9,label:e[1],color:s.parameters.values[a]};return i[t.layout.track_split_field]=r,i}))}}render(){this._applyLayoutOptions(),this._previous_categories=this._categories;const[t,e]=this._assignTracks(this.data);this._categories=t;if(!t.every(((t,e)=>t===this._previous_categories[e])))return void this.updateSplitTrackAxis(t);const l=this._applyFilters(e);this._statusnodes_group.selectAll("rect").remove();const n=this._statusnodes_group.selectAll("rect").data(a.range(t.length));if(this.layout.split_tracks){const t=this.getTrackHeight();n.enter().append("rect").attr("class","lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared").attr("rx",this.layout.bounding_box_padding).attr("ry",this.layout.bounding_box_padding).merge(n).attr("id",(t=>this.getElementStatusNodeId(t))).attr("x",0).attr("y",(e=>e*t)).attr("width",this.parent.layout.cliparea.width).attr("height",Math.max(t-this.layout.track_vertical_spacing,1))}n.exit().remove();const o=this._datanodes_group.selectAll("rect").data(l,(t=>t[this.layout.id_field]));o.enter().append("rect").merge(o).attr("id",(t=>this.getElementId(t))).attr("x",(t=>t[r])).attr("y",(t=>t[s])).attr("width",(t=>Math.max(t[i]-t[r],1))).attr("height",this.layout.track_height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),o.exit().remove(),this._datanodes_group.call(this.applyBehaviors.bind(this)),this.parent&&this.parent.legend&&this.parent.legend.render()}_getTooltipPosition(t){return{x_min:t.data[r],x_max:t.data[i],y_min:t.data[s],y_max:t.data[l]}}updateSplitTrackAxis(t){const e=!!this.layout.track_split_legend_to_y_axis&&`y${this.layout.track_split_legend_to_y_axis}`;if(this.layout.split_tracks){const a=+t.length||0,r=+this.layout.track_height||0,s=2*(+this.layout.bounding_box_padding||0)+(+this.layout.track_vertical_spacing||0),i=a*r+(a-1)*s;this.parent.scaleHeightToData(i),e&&this.parent.legend&&(this.parent.legend.hide(),this.parent.layout.axes[e]={render:!0,ticks:[],range:{start:i-this.layout.track_height/2,end:this.layout.track_height/2}},this.layout.legend.forEach((r=>{const s=r[this.layout.track_split_field];let i=t.findIndex((t=>t===s));-1!==i&&("DESC"===this.layout.track_split_order&&(i=Math.abs(i-a-1)),this.parent.layout.axes[e].ticks.push({y:i-1,text:r.label}))})),this.layout.y_axis={axis:this.layout.track_split_legend_to_y_axis,floor:1,ceiling:a}),this.parent_plot.positionPanels()}else e&&this.parent.legend&&(this.layout.always_hide_legend||this.parent.legend.show(),this.parent.layout.axes[e]={render:!1},this.parent.render());return this}toggleSplitTracks(){return this.layout.split_tracks=!this.layout.split_tracks,this.parent.legend&&!this.layout.always_hide_legend&&(this.parent.layout.margin.bottom=5+(this.layout.split_tracks?0:this.parent.legend.layout.height+5)),this.render(),this}_makeColorScheme(t){if(t.find((t=>t[2])))return t.map((t=>c(0,t[2])));const e=t.length;return e<=15?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(233,150,122)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(50,205,50)","rgb(255,69,0)","rgb(255,0,0)"]:e<=18?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]:["rgb(212,212,212)","rgb(128,128,128)","rgb(112,48,160)","rgb(230,184,183)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,102)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,150,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]}_generateCategoriesFromData(t,e){const a=this,r=this._base_layout.legend;if(r&&r.length)return r.map((t=>[t[this.layout.track_split_field],t.label,t.color]));const s={},i=[];return t.forEach((t=>{const r=t[a.layout.track_split_field];Object.prototype.hasOwnProperty.call(s,r)||(s[r]=null,i.push([r,t[this.layout.track_label_field],t[e]]))})),i}}),t.Layouts.add("tooltip","standard_intervals",_),t.Layouts.add("data_layer","intervals",h),t.Layouts.add("data_layer","bed_intervals",u),t.Layouts.add("panel","intervals",p),t.Layouts.add("panel","bed_intervals",y),t.Layouts.add("plot","interval_association",b),t.ScaleFunctions.add("to_rgb",c),t.Widgets.add("toggle_split_tracks",class extends o{constructor(t){if(super(...arguments),t.data_layer_id||(t.data_layer_id="intervals"),!this.parent_panel.data_layers[t.data_layer_id])throw new Error("Toggle split tracks widget specifies an invalid data layer ID")}update(){const t=this.parent_panel.data_layers[this.layout.data_layer_id],e=t.layout.split_tracks?"Merge Tracks":"Split Tracks";return this.button?(this.button.setHtml(e),this.button.show(),this.parent.position(),this):(this.button=new n(this).setColor(this.layout.color).setHtml(e).setTitle("Toggle whether tracks are split apart or merged together").setOnclick((()=>{t.toggleSplitTracks(),this.scale_timeout&&clearTimeout(this.scale_timeout),this.scale_timeout=setTimeout((()=>{this.parent_panel.scaleHeightToData(),this.parent_plot.positionPanels()}),0),this.update()})),this.update())}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const o=n;LzIntervalsTrack=e.default})(); //# sourceMappingURL=lz-intervals-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-track.min.js.map b/dist/ext/lz-intervals-track.min.js.map index cae22cd9..607d428b 100644 --- a/dist/ext/lz-intervals-track.min.js.map +++ b/dist/ext/lz-intervals-track.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/ext/lz-intervals-track.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","XCS","Symbol","for","YCS","XCE","YCE","install","LocusZoom","BaseApiAdapter","Adapters","_Button","Widgets","_BaseWidget","default_layout","start_field","end_field","track_label_field","track_split_field","track_split_order","track_split_legend_to_y_axis","split_tracks","track_height","track_vertical_spacing","bounding_box_padding","always_hide_legend","color","fill_opacity","tooltip_positioning","BaseLayer","DataLayers","intervals_tooltip_layout","namespace","closable","show","or","hide","and","html","intervals_layer_layout","id","type","tag","fields","id_field","field","scale_function","parameters","categories","values","null_value","legend","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip","intervals_panel_layout","min_height","height","margin","top","right","bottom","left","toolbar","l","Layouts","unnamespaced","widgets","push","data_layer_id","position","axes","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","hidden","orientation","origin","x","y","pad_from_bottom","data_layers","intervals_plot_layout","state","width","responsive_resize","min_region_scale","max_region_scale","panels","merge","add","chain","query","header","bedtracksource","this","params","source","chr","end","start","url","layout","super","arguments","_previous_categories","_categories","initialize","_statusnodes_group","svg","group","append","attr","_datanodes_group","data","result","forEach","item","item_key","allow_overlap","grouped_data","index","i","length","row_to_test","last_item","x_scale","parent","_arrangeTrackSplit","_arrangeTracksLinear","keys","reverse","row_index","getTrackHeight","track","reduce","acc","val","concat","element","getBaseId","replace","self","base_layout","_base_layout","render_layout","base_color_scale","find","color_scale","Error","has_colors","has_legend","rgb_option","rgb_field","known_categories","_generateCategoriesFromData","colors","_makeColorScheme","map","pair","shape","label","_applyLayoutOptions","assigned_data","_assignTracks","every","updateSplitTrackAxis","track_data","_applyFilters","selectAll","remove","status_nodes","enter","d","getElementStatusNodeId","cliparea","exit","data_nodes","getElementId","resolveScalableParameter","applyBehaviors","bind","render","x_min","x_max","y_min","y_max","legend_axis","tracks","track_spacing","target_height","scaleHeightToData","ticks","range","findIndex","Math","abs","text","y_axis","axis","floor","ceiling","parent_plot","positionPanels","category_info","n_categories","unique_ids","ScaleFunctions","value","parent_panel","data_layer","button","setHtml","setColor","setTitle","setOnclick","toggleSplitTracks","scale_timeout","clearTimeout","setTimeout","update","use"],"mappings":"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","file":"ext/lz-intervals-track.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Interval annotation track (for chromatin state, etc). Useful for BED file data with non-overlapping intervals.\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~IntervalLZ}\n * * {@link module:LocusZoom_Widgets~toggle_split_tracks}\n * * {@link module:LocusZoom_ScaleFunctions~to_rgb}\n * * {@link module:LocusZoom_DataLayers~intervals}\n * * {@link module:LocusZoom_Layouts~standard_intervals}\n * * {@link module:LocusZoom_Layouts~intervals_layer}\n * * {@link module:LocusZoom_Layouts~intervals}\n * * {@link module:LocusZoom_Layouts~interval_association}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import IntervalsTrack from 'locuszoom/esm/ext/lz-intervals-track';\n * LocusZoom.use(IntervalsTrack);\n * ```\n *\n * Then use the features made available by this extension. (see demos and documentation for guidance)\n * @module\n */\n\nimport * as d3 from 'd3';\n\n\n// Coordinates (start, end) are cached to facilitate rendering\nconst XCS = Symbol.for('lzXCS');\nconst YCS = Symbol.for('lzYCS');\nconst XCE = Symbol.for('lzXCE');\nconst YCE = Symbol.for('lzYCE');\n\n\nfunction install (LocusZoom) {\n const BaseApiAdapter = LocusZoom.Adapters.get('BaseApiAdapter');\n const _Button = LocusZoom.Widgets.get('_Button');\n const _BaseWidget = LocusZoom.Widgets.get('BaseWidget');\n\n /**\n * (**extension**) Retrieve Interval Annotation Data (e.g. BED Tracks), as fetched from the LocusZoom API server (or compatible)\n * @public\n * @alias module:LocusZoom_Adapters~IntervalLZ\n * @see module:LocusZoom_Adapters~BaseApiAdapter\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n * @param {number} config.params.source The numeric ID for a specific dataset as assigned by the API server\n */\n class IntervalLZ extends BaseApiAdapter {\n getURL(state, chain, fields) {\n const source = chain.header.bedtracksource || this.params.source;\n const query = `?filter=id in ${source} and chromosome eq '${state.chr}' and start le ${state.end} and end ge ${state.start}`;\n return `${this.url}${query}`;\n }\n }\n\n /**\n * (**extension**) Button to toggle split tracks mode in an intervals track. This button only works as a panel-level toolbar\n * and when used with an intervals data layer from this extension.\n * @alias module:LocusZoom_Widgets~toggle_split_tracks\n * @see module:LocusZoom_Widgets~BaseWidget\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n class ToggleSplitTracks extends _BaseWidget {\n /**\n * @param {string} layout.data_layer_id The ID of the data layer that this button is intended to control.\n */\n constructor(layout) {\n super(...arguments);\n if (!layout.data_layer_id) {\n layout.data_layer_id = 'intervals';\n }\n if (!this.parent_panel.data_layers[layout.data_layer_id]) {\n throw new Error('Toggle split tracks widget specifies an invalid data layer ID');\n }\n }\n\n update() {\n const data_layer = this.parent_panel.data_layers[this.layout.data_layer_id];\n const html = data_layer.layout.split_tracks ? 'Merge Tracks' : 'Split Tracks';\n if (this.button) {\n this.button.setHtml(html);\n this.button.show();\n this.parent.position();\n return this;\n } else {\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(html)\n .setTitle('Toggle whether tracks are split apart or merged together')\n .setOnclick(() => {\n data_layer.toggleSplitTracks();\n // FIXME: the timeout calls to scale and position (below) cause full ~5 additional re-renders\n // If we can remove these it will greatly speed up re-rendering.\n // The key problem here is that the height is apparently not known in advance and is determined after re-render.\n if (this.scale_timeout) {\n clearTimeout(this.scale_timeout);\n }\n this.scale_timeout = setTimeout(() => {\n this.parent_panel.scaleHeightToData();\n this.parent_plot.positionPanels();\n }, 0);\n this.update();\n });\n return this.update();\n }\n }\n }\n\n\n /**\n * (**extension**) Convert a value \"\"rr,gg,bb\" (if given) to a css-friendly color string: \"rgb(rr,gg,bb)\".\n * This is tailored specifically to the color specification format embraced by the BED file standard.\n * @alias module:LocusZoom_ScaleFunctions~to_rgb\n * @param {Object} parameters This function has no defined configuration options\n * @param {String|null} value The value to convert to rgb\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n function to_rgb(parameters, value) {\n return value ? `rgb(${value})` : null;\n }\n\n const default_layout = {\n start_field: 'start',\n end_field: 'end',\n track_label_field: 'state_name', // Used to label items on the y-axis\n // Used to uniquely identify tracks for coloring. This tends to lead to more stable coloring/sorting\n // than using the label field- eg, state_ids allow us to set global colors across the entire dataset,\n // not just choose unique colors within a particular narrow region. (where changing region might lead to more\n // categories and different colors)\n track_split_field: 'state_id',\n track_split_order: 'DESC',\n track_split_legend_to_y_axis: 2,\n split_tracks: true,\n track_height: 15,\n track_vertical_spacing: 3,\n bounding_box_padding: 2,\n always_hide_legend: false,\n color: '#B8B8B8',\n fill_opacity: 1,\n tooltip_positioning: 'vertical',\n };\n\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n\n /**\n * (**extension**) Implements a data layer that will render interval annotation tracks (intervals must provide start and end values)\n * Each interval (such as from a BED file) will be rendered as a rectangle. All spans can be rendered on the same\n * row, or each (auto-detected) category can be rendered as one row per category.\n *\n * This layer is intended to work with a variety of datasets with special requirements. As such, it has a lot\n * of configuration options devoted to identifying how to fill in missing information (such as color)\n *\n * @alias module:LocusZoom_DataLayers~intervals\n * @see module:LocusZoom_DataLayers~BaseDataLayer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n class LzIntervalsTrack extends BaseLayer {\n /**\n * @param {string} [layout.start_field='start'] The field that defines interval start position\n * @param {string} [layout.end_field='end'] The field that defines interval end position\n * @param {string} [layout.track_label_field='state_name'] Used to label items on the y-axis\n * @param {string} [layout.track_split_field='state_id'] Used to define categories on the y-axis. It is usually most convenient to use\n * the same value for state_field and label_field (eg 1:1 correspondence).\n * @param {*|'DESC'} [layout.track_split_order='DESC'] When in split tracks mode, should categories be shown in\n * the order given, or descending order\n * @param {number} [layout.track_split_legend_to_y_axis=2]\n * @param {boolean} [layout.split_tracks=true] Whether to show tracks as merged (one row) or split (many rows)\n * on initial render.\n * @param {number} [layout.track_height=15] The height of each interval rectangle, in px\n * @param {number} [layout.track_vertical_spacing=3]\n * @param {number} [layout.bounding_box_padding=2]\n * @param {boolean} [layout.always_hide_legend=false] Normally the legend is shown in merged mode and hidden\n * in split mode. For datasets with a very large number of categories, it may make sense to hide the legend at all times.\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#B8B8B8'] The color of each datum rectangle\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1]\n * @param {string} [layout.tooltip_positioning='vertical']\n */\n constructor(layout) {\n LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n this._previous_categories = [];\n this._categories = [];\n }\n\n initialize() {\n super.initialize();\n this._statusnodes_group = this.svg.group.append('g')\n .attr('class', 'lz-data-layer-intervals lz-data-layer-intervals-statusnode');\n this._datanodes_group = this.svg.group.append('g')\n .attr('class', 'lz-data_layer-intervals');\n }\n\n /**\n * Split data into tracks such that anything with a common grouping field is in the same track\n * @param data\n * @return {unknown[]}\n * @private\n */\n _arrangeTrackSplit(data) {\n const {track_split_field} = this.layout;\n const result = {};\n data.forEach((item) => {\n const item_key = item[track_split_field];\n if (!Object.prototype.hasOwnProperty.call(result, item_key)) {\n result[item_key] = [];\n }\n result[item_key].push(item);\n });\n return result;\n }\n\n /**\n * Split data into rows using a simple greedy algorithm such that no two items overlap (share same interval)\n * Assumes that the data are sorted so item1.start always <= item2.start.\n *\n * This function can also simply return all data on a single row. This functionality may become configurable\n * in the future but for now reflects a lack of clarity in the requirements/spec. The code to split\n * overlapping items is present but may not see direct use.\n */\n _arrangeTracksLinear(data, allow_overlap = true) {\n if (allow_overlap) {\n // If overlap is allowed, then all the data can live on a single row\n return [data];\n }\n\n // ASSUMPTION: Data is given to us already sorted by start position to facilitate grouping.\n // We do not sort here because JS \"sort\" is not stable- if there are many intervals that overlap, then we\n // can get different layouts (number/order of rows) on each call to \"render\".\n //\n // At present, we decide how to update the y-axis based on whether current and former number of rows are\n // the same. An unstable sort leads to layout thrashing/too many re-renders. FIXME: don't rely on counts\n const {start_field, end_field} = this.layout;\n\n const grouped_data = [[]]; // Prevent two items from colliding by rendering them to different rows, like genes\n data.forEach((item, index) => {\n for (let i = 0; i < grouped_data.length; i++) {\n // Iterate over all rows of the\n const row_to_test = grouped_data[i];\n const last_item = row_to_test[row_to_test.length - 1];\n // Some programs report open intervals, eg 0-1,1-2,2-3; these points are not considered to overlap (hence the test isn't \"<=\")\n const has_overlap = last_item && (item[start_field] < last_item[end_field]) && (last_item[start_field] < item[end_field]);\n if (!has_overlap) {\n // If there is no overlap, add item to current row, and move on to the next item\n row_to_test.push(item);\n return;\n }\n }\n // If this item would collide on all existing rows, create a new row\n grouped_data.push([item]);\n });\n return grouped_data;\n }\n\n /**\n * Annotate each item with the track number, and return.\n * @param {Object[]}data\n * @private\n * @return [String[], Object[]] Return the categories and the data array\n */\n _assignTracks(data) {\n // Flatten the grouped data.\n const {x_scale} = this.parent;\n const {start_field, end_field, bounding_box_padding, track_height} = this.layout;\n\n const grouped_data = this.layout.split_tracks ? this._arrangeTrackSplit(data) : this._arrangeTracksLinear(data, true);\n const categories = Object.keys(grouped_data);\n if (this.layout.track_split_order === 'DESC') {\n categories.reverse();\n }\n\n categories.forEach((key, row_index) => {\n const row = grouped_data[key];\n row.forEach((item) => {\n item[XCS] = x_scale(item[start_field]);\n item[XCE] = x_scale(item[end_field]);\n item[YCS] = row_index * this.getTrackHeight() + bounding_box_padding;\n item[YCE] = item[YCS] + track_height;\n // Store the row ID, so that clicking on a point can find the right status node (big highlight box)\n item.track = row_index;\n });\n });\n // We're mutating elements of the original data array as a side effect: the return value here is\n // interchangeable with `this.data` for subsequent usages\n // TODO: Can replace this with array.flat once polyfill support improves\n return [categories, Object.values(grouped_data).reduce((acc, val) => acc.concat(val), [])];\n }\n\n /**\n * When we are in \"split tracks mode\", it's convenient to wrap all individual annotations with a shared\n * highlight box that wraps everything on that row.\n *\n * This is done automatically by the \"setElementStatus\" code, if this function returns a non-null value\n *\n * To define shared highlighting on the track split field define the status node id override\n * to generate an ID common to the track when we're actively splitting data out to separate tracks\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n if (this.layout.split_tracks) {\n // Data nodes are bound to data objects, but the \"status_nodes\" selection is bound to numeric row IDs\n const track = typeof element === 'object' ? element.track : element;\n const base = `${this.getBaseId()}-statusnode-${track}`;\n return base.replace(/[^\\w]/g, '_');\n }\n // In merged tracks mode, there is no separate status node\n return null;\n }\n\n // Helper function to sum layout values to derive total height for a single interval track\n getTrackHeight() {\n return this.layout.track_height\n + this.layout.track_vertical_spacing\n + (2 * this.layout.bounding_box_padding);\n }\n\n // Modify the layout as necessary to ensure that appropriate color, label, and legend options are available\n // Even when not displayed, the legend is used to generate the y-axis ticks\n _applyLayoutOptions() {\n const self = this;\n const base_layout = this._base_layout;\n const render_layout = this.layout;\n const base_color_scale = base_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'categorical_bin';\n });\n const color_scale = render_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'categorical_bin';\n });\n if (!base_color_scale) {\n // This can be a placeholder (empty categories & values), but it needs to be there\n throw new Error('Interval tracks must define a `categorical_bin` color scale');\n }\n\n const has_colors = base_color_scale.parameters.categories.length && base_color_scale.parameters.values.length;\n const has_legend = base_layout.legend && base_layout.legend.length;\n\n if (!!has_colors ^ !!has_legend) {\n // Don't allow color OR legend to be set manually. It must be both, or neither.\n throw new Error('To use a manually specified color scheme, both color and legend options must be set.');\n }\n\n // Harvest any information about an explicit color field that should be considered when generating colors\n const rgb_option = base_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'to_rgb';\n });\n const rgb_field = rgb_option && rgb_option.field;\n\n // Auto-generate legend based on data\n const known_categories = this._generateCategoriesFromData(this.data, rgb_field); // [id, label, itemRgb] items\n\n if (!has_colors && !has_legend) {\n // If no color scheme pre-defined, then make a color scheme that is appropriate and apply to the plot\n // The legend must match the color scheme. If we generate one, then we must generate both.\n\n const colors = this._makeColorScheme(known_categories);\n color_scale.parameters.categories = known_categories.map(function (item) {\n return item[0];\n });\n color_scale.parameters.values = colors;\n\n this.layout.legend = known_categories.map(function (pair, index) {\n const id = pair[0];\n const label = pair[1];\n const item_color = color_scale.parameters.values[index];\n const item = { shape: 'rect', width: 9, label: label, color: item_color };\n item[self.layout.track_split_field] = id;\n return item;\n });\n }\n }\n\n // Implement the main render function\n render() {\n //// Autogenerate layout options if not provided\n this._applyLayoutOptions();\n\n // Determine the appropriate layout for tracks. Store the previous categories (y axis ticks) to decide\n // whether the axis needs to be re-rendered.\n this._previous_categories = this._categories;\n const [categories, assigned_data] = this._assignTracks(this.data);\n this._categories = categories;\n // Update the legend axis if the number of ticks changed\n const labels_changed = !categories.every( (item, index) => item === this._previous_categories[index]);\n if (labels_changed) {\n this.updateSplitTrackAxis(categories);\n return;\n }\n\n // Apply filters to only render a specified set of points. Hidden fields will still be given space to render, but not shown.\n const track_data = this._applyFilters(assigned_data);\n\n // Clear before every render so that, eg, highlighting doesn't persist if we load a region with different\n // categories (row 2 might be a different category and it's confusing if the row stays highlighted but changes meaning)\n // Highlighting will automatically get added back if it actually makes sense, courtesy of setElementStatus,\n // if a selected item is still in view after the new region loads.\n this._statusnodes_group.selectAll('rect')\n .remove();\n\n // Reselect in order to add new data\n const status_nodes = this._statusnodes_group.selectAll('rect')\n .data(d3.range(categories.length));\n\n if (this.layout.split_tracks) {\n // Status nodes: a big highlight box around all items of the same type. Used in split tracks mode,\n // because everything on the same row is the same category and a group makes sense\n // There are no status nodes in merged mode, because the same row contains many kinds of things\n\n // Status nodes are 1 per row, so \"data\" can just be a dummy list of possible row IDs\n // Each status node is a box that runs the length of the panel and receives a special \"colored box\" css\n // style when selected\n const height = this.getTrackHeight();\n status_nodes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared')\n .attr('rx', this.layout.bounding_box_padding)\n .attr('ry', this.layout.bounding_box_padding)\n .merge(status_nodes)\n .attr('id', (d) => this.getElementStatusNodeId(d))\n .attr('x', 0)\n .attr('y', (d) => (d * height))\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', height - this.layout.track_vertical_spacing);\n }\n status_nodes.exit()\n .remove();\n\n // Draw rectangles for the data (intervals)\n const data_nodes = this._datanodes_group.selectAll('rect')\n .data(track_data, (d) => d[this.layout.id_field]);\n\n data_nodes.enter()\n .append('rect')\n .merge(data_nodes)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => d[XCS])\n .attr('y', (d) => d[YCS])\n .attr('width', (d) => d[XCE] - d[XCS])\n .attr('height', this.layout.track_height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n data_nodes.exit()\n .remove();\n\n this._datanodes_group\n .call(this.applyBehaviors.bind(this));\n\n // The intervals track allows legends to be dynamically generated, in which case space can only be\n // allocated after the panel has been rendered.\n if (this.parent && this.parent.legend) {\n this.parent.legend.render();\n }\n }\n\n _getTooltipPosition(tooltip) {\n return {\n x_min: tooltip.data[XCS],\n x_max: tooltip.data[XCE],\n y_min: tooltip.data[YCS],\n y_max: tooltip.data[YCE],\n };\n }\n\n // Redraw split track axis or hide it, and show/hide the legend, as determined\n // by current layout parameters and data\n updateSplitTrackAxis(categories) {\n const legend_axis = this.layout.track_split_legend_to_y_axis ? `y${this.layout.track_split_legend_to_y_axis}` : false;\n if (this.layout.split_tracks) {\n const tracks = +categories.length || 0;\n const track_height = +this.layout.track_height || 0;\n const track_spacing = 2 * (+this.layout.bounding_box_padding || 0) + (+this.layout.track_vertical_spacing || 0);\n const target_height = (tracks * track_height) + ((tracks - 1) * track_spacing);\n this.parent.scaleHeightToData(target_height);\n if (legend_axis && this.parent.legend) {\n this.parent.legend.hide();\n this.parent.layout.axes[legend_axis] = {\n render: true,\n ticks: [],\n range: {\n start: (target_height - (this.layout.track_height / 2)),\n end: (this.layout.track_height / 2),\n },\n };\n // There is a very tight coupling between the display directives: each legend item must identify a key\n // field for unique tracks. (Typically this is `state_id`, the same key field used to assign unique colors)\n // The list of unique keys corresponds to the order along the y-axis\n this.layout.legend.forEach((element) => {\n const key = element[this.layout.track_split_field];\n let track = categories.findIndex((item) => item === key);\n if (track !== -1) {\n if (this.layout.track_split_order === 'DESC') {\n track = Math.abs(track - tracks - 1);\n }\n this.parent.layout.axes[legend_axis].ticks.push({\n y: track - 1,\n text: element.label,\n });\n }\n });\n this.layout.y_axis = {\n axis: this.layout.track_split_legend_to_y_axis,\n floor: 1,\n ceiling: tracks,\n };\n }\n // This will trigger a re-render\n this.parent_plot.positionPanels();\n } else {\n if (legend_axis && this.parent.legend) {\n if (!this.layout.always_hide_legend) {\n this.parent.legend.show();\n }\n this.parent.layout.axes[legend_axis] = { render: false };\n this.parent.render();\n }\n }\n return this;\n }\n\n // Method to not only toggle the split tracks boolean but also update\n // necessary display values to animate a complete merge/split\n toggleSplitTracks() {\n this.layout.split_tracks = !this.layout.split_tracks;\n if (this.parent.legend && !this.layout.always_hide_legend) {\n this.parent.layout.margin.bottom = 5 + (this.layout.split_tracks ? 0 : this.parent.legend.layout.height + 5);\n }\n this.render();\n return this;\n }\n\n // Choose an appropriate color scheme based on the number of items in the track, and whether or not we are\n // using explicitly provided itemRgb information\n _makeColorScheme(category_info) {\n // If at least one element has an explicit itemRgb, assume the entire dataset has colors\n const has_explicit_colors = category_info.find((item) => item[2]);\n if (has_explicit_colors) {\n return category_info.map((item) => item[2]);\n }\n\n // Use a set of color schemes for common 15, 18, or 25 state models, as specified from:\n // https://egg2.wustl.edu/roadmap/web_portal/chr_state_learning.html\n // These are actually reversed so that dim colors come first, on the premise that usually these are the\n // most common states\n const n_categories = category_info.length;\n if (n_categories <= 15) {\n return ['rgb(212,212,212)', 'rgb(192,192,192)', 'rgb(128,128,128)', 'rgb(189,183,107)', 'rgb(233,150,122)', 'rgb(205,92,92)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,0)', 'rgb(194,225,5)', 'rgb(0,100,0)', 'rgb(0,128,0)', 'rgb(50,205,50)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n } else if (n_categories <= 18) {\n return ['rgb(212,212,212)', 'rgb(192,192,192)', 'rgb(128,128,128)', 'rgb(189,183,107)', 'rgb(205,92,92)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,0)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(0,100,0)', 'rgb(0,128,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n } else {\n // If there are more than 25 categories, the interval layer will fall back to the 'null value' option\n return ['rgb(212,212,212)', 'rgb(128,128,128)', 'rgb(112,48,160)', 'rgb(230,184,183)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,102)', 'rgb(255,255,0)', 'rgb(255,255,0)', 'rgb(255,255,0)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(0,150,0)', 'rgb(0,128,0)', 'rgb(0,128,0)', 'rgb(0,128,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n }\n }\n\n /**\n * Find all of the unique tracks (a combination of name and ID information)\n * @param {Object} data\n * @param {String} [rgb_field] A field that contains an RGB value. Aimed at BED files with an itemRgb column\n * @private\n * @returns {Array} All [unique_id, label, color] pairs in data. The unique_id is the thing used to define groupings\n * most unambiguously.\n */\n _generateCategoriesFromData(data, rgb_field) {\n const self = this;\n // Use the hard-coded legend if available (ignoring any mods on re-render)\n const legend = this._base_layout.legend;\n if (legend && legend.length) {\n return legend.map((item) => [item[this.layout.track_split_field], item.label, item.color]);\n }\n\n // Generate options from data, if no preset legend exists\n const unique_ids = {}; // make categories unique\n const categories = [];\n\n data.forEach((item) => {\n const id = item[self.layout.track_split_field];\n if (!Object.prototype.hasOwnProperty.call(unique_ids, id)) {\n unique_ids[id] = null;\n // If rgbfield is null, then the last entry is undefined/null as well\n categories.push([id, item[this.layout.track_label_field], item[rgb_field]]);\n }\n });\n return categories;\n }\n }\n\n /**\n * (**extension**) A basic tooltip with information to be shown over an intervals datum\n * @alias module:LocusZoom_Layouts~standard_intervals\n * @type tooltip\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_tooltip_layout = {\n namespace: { 'intervals': 'intervals' },\n closable: false,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{{{namespace[intervals]}}state_name|htmlescape}}
{{{{namespace[intervals]}}start|htmlescape}}-{{{{namespace[intervals]}}end|htmlescape}}',\n };\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for chromHMM output,\n * in which various states are assigned numeric state IDs and (<= as many) text state names\n * @alias module:LocusZoom_Layouts~intervals_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_layer_layout = {\n namespace: { 'intervals': 'intervals' },\n id: 'intervals',\n type: 'intervals',\n tag: 'intervals',\n fields: ['{{namespace[intervals]}}start', '{{namespace[intervals]}}end', '{{namespace[intervals]}}state_id', '{{namespace[intervals]}}state_name', '{{namespace[intervals]}}itemRgb'],\n id_field: '{{namespace[intervals]}}start', // FIXME: This is not a good D3 \"are these datums redundant\" ID for datasets with multiple intervals heavily overlapping\n start_field: '{{namespace[intervals]}}start',\n end_field: '{{namespace[intervals]}}end',\n track_split_field: '{{namespace[intervals]}}state_name',\n track_label_field: '{{namespace[intervals]}}state_name',\n split_tracks: false,\n always_hide_legend: true,\n color: [\n {\n // If present, an explicit color field will override any other option (and be used to auto-generate legend)\n field: '{{namespace[intervals]}}itemRgb',\n scale_function: 'to_rgb',\n },\n {\n // TODO: Consider changing this to stable_choice in the future, for more stable coloring\n field: '{{namespace[intervals]}}state_name',\n scale_function: 'categorical_bin',\n parameters: {\n // Placeholder. Empty categories and values will automatically be filled in when new data loads.\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n },\n ],\n legend: [], // Placeholder; auto-filled when data loads.\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: intervals_tooltip_layout,\n };\n\n /**\n * (**extension**) A panel containing an intervals data layer, eg for BED tracks\n * @alias module:LocusZoom_Layouts~intervals\n * @type panel\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_panel_layout = {\n id: 'intervals',\n tag: 'intervals',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 150, bottom: 5, left: 50 },\n toolbar: (function () {\n const l = LocusZoom.Layouts.get('toolbar', 'standard_panel', { unnamespaced: true });\n l.widgets.push({\n type: 'toggle_split_tracks',\n data_layer_id: 'intervals',\n position: 'right',\n });\n return l;\n })(),\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n legend: {\n hidden: true,\n orientation: 'horizontal',\n origin: { x: 50, y: 0 },\n pad_from_bottom: 5,\n },\n data_layers: [intervals_layer_layout],\n };\n\n /**\n * (**extension**) A plot layout that shows association summary statistics, genes, and interval data. This example assumes\n * chromHMM data. (see panel layout)\n * @alias module:LocusZoom_Layouts~interval_association\n * @type plot\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_plot_layout = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association', { unnamespaced: true }),\n panels: [\n LocusZoom.Layouts.get('panel', 'association'),\n LocusZoom.Layouts.merge({ unnamespaced: true, min_height: 120, height: 120 }, intervals_panel_layout),\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n\n LocusZoom.Adapters.add('IntervalLZ', IntervalLZ);\n LocusZoom.DataLayers.add('intervals', LzIntervalsTrack);\n\n LocusZoom.Layouts.add('tooltip', 'standard_intervals', intervals_tooltip_layout);\n LocusZoom.Layouts.add('data_layer', 'intervals', intervals_layer_layout);\n LocusZoom.Layouts.add('panel', 'intervals', intervals_panel_layout);\n LocusZoom.Layouts.add('plot', 'interval_association', intervals_plot_layout);\n\n LocusZoom.ScaleFunctions.add('to_rgb', to_rgb);\n\n LocusZoom.Widgets.add('toggle_split_tracks', ToggleSplitTracks);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/ext/lz-intervals-track.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","XCS","Symbol","for","YCS","XCE","YCE","install","LocusZoom","BaseUMAdapter","Adapters","_Button","Widgets","_BaseWidget","to_rgb","parameters","value","default_layout","start_field","end_field","track_label_field","track_split_field","track_split_order","track_split_legend_to_y_axis","split_tracks","track_height","track_vertical_spacing","bounding_box_padding","always_hide_legend","color","fill_opacity","tooltip_positioning","BaseLayer","DataLayers","intervals_tooltip_layout","closable","show","or","hide","and","html","intervals_layer_layout","namespace","id","type","tag","id_field","field","scale_function","categories","values","null_value","legend","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip","bed_intervals_layer_layout","Layouts","merge","intervals_panel_layout","min_height","height","margin","top","right","bottom","left","toolbar","l","widgets","push","data_layer_id","position","axes","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","hidden","orientation","origin","x","y","pad_from_bottom","data_layers","bed_intervals_panel_layout","intervals_plot_layout","state","width","responsive_resize","min_region_scale","max_region_scale","panels","add","request_options","query","this","_config","source","chr","end","start","super","_getURL","layout","arguments","_previous_categories","_categories","initialize","_statusnodes_group","svg","group","append","attr","_datanodes_group","data","result","forEach","item","item_key","allow_overlap","grouped_data","index","i","length","row_to_test","last_item","x_scale","parent","_arrangeTrackSplit","_arrangeTracksLinear","keys","reverse","row_index","getTrackHeight","track","reduce","acc","val","concat","element","getBaseId","replace","self","base_layout","_base_layout","render_layout","base_color_scale","find","color_scale","Error","has_colors","has_legend","rgb_option","rgb_field","known_categories","_generateCategoriesFromData","colors","_makeColorScheme","map","pair","shape","label","_applyLayoutOptions","assigned_data","_assignTracks","every","updateSplitTrackAxis","track_data","_applyFilters","selectAll","remove","status_nodes","enter","d","getElementStatusNodeId","cliparea","Math","max","exit","data_nodes","getElementId","resolveScalableParameter","applyBehaviors","bind","render","x_min","x_max","y_min","y_max","legend_axis","tracks","track_spacing","target_height","scaleHeightToData","ticks","range","findIndex","abs","text","y_axis","axis","floor","ceiling","parent_plot","positionPanels","category_info","n_categories","unique_ids","ScaleFunctions","parent_panel","data_layer","button","setHtml","setColor","setTitle","setOnclick","toggleSplitTracks","scale_timeout","clearTimeout","setTimeout","update","use"],"mappings":"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","file":"ext/lz-intervals-track.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Interval annotation track (for chromatin state, etc). Useful for BED file data with non-overlapping intervals.\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~IntervalLZ}\n * * {@link module:LocusZoom_Widgets~toggle_split_tracks}\n * * {@link module:LocusZoom_ScaleFunctions~to_rgb}\n * * {@link module:LocusZoom_DataLayers~intervals}\n * * {@link module:LocusZoom_Layouts~standard_intervals}\n * * {@link module:LocusZoom_Layouts~bed_intervals_layer}\n * * {@link module:LocusZoom_Layouts~intervals_layer}\n * * {@link module:LocusZoom_Layouts~intervals}\n * * {@link module:LocusZoom_Layouts~bed_intervals}\n * * {@link module:LocusZoom_Layouts~interval_association}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import IntervalsTrack from 'locuszoom/esm/ext/lz-intervals-track';\n * LocusZoom.use(IntervalsTrack);\n * ```\n *\n * Then use the features made available by this extension. (see demos and documentation for guidance)\n * @module\n */\n\nimport * as d3 from 'd3';\n\n\n// Coordinates (start, end) are cached to facilitate rendering\nconst XCS = Symbol.for('lzXCS');\nconst YCS = Symbol.for('lzYCS');\nconst XCE = Symbol.for('lzXCE');\nconst YCE = Symbol.for('lzYCE');\n\n\nfunction install (LocusZoom) {\n const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter');\n const _Button = LocusZoom.Widgets.get('_Button');\n const _BaseWidget = LocusZoom.Widgets.get('BaseWidget');\n\n /**\n * (**extension**) Retrieve Interval Annotation Data (e.g. BED Tracks), as fetched from the LocusZoom API server (or compatible)\n * @public\n * @alias module:LocusZoom_Adapters~IntervalLZ\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n * @param {number} config.params.source The numeric ID for a specific dataset as assigned by the API server\n */\n class IntervalLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const source = this._config.source;\n const query = `?filter=id in ${source} and chromosome eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}`;\n\n const base = super._getURL(request_options);\n return `${base}${query}`;\n }\n }\n\n /**\n * (**extension**) Button to toggle split tracks mode in an intervals track. This button only works as a panel-level toolbar\n * and when used with an intervals data layer from this extension.\n * @alias module:LocusZoom_Widgets~toggle_split_tracks\n * @see module:LocusZoom_Widgets~BaseWidget\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n class ToggleSplitTracks extends _BaseWidget {\n /**\n * @param {string} layout.data_layer_id The ID of the data layer that this button is intended to control.\n */\n constructor(layout) {\n super(...arguments);\n if (!layout.data_layer_id) {\n layout.data_layer_id = 'intervals';\n }\n if (!this.parent_panel.data_layers[layout.data_layer_id]) {\n throw new Error('Toggle split tracks widget specifies an invalid data layer ID');\n }\n }\n\n update() {\n const data_layer = this.parent_panel.data_layers[this.layout.data_layer_id];\n const html = data_layer.layout.split_tracks ? 'Merge Tracks' : 'Split Tracks';\n if (this.button) {\n this.button.setHtml(html);\n this.button.show();\n this.parent.position();\n return this;\n } else {\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(html)\n .setTitle('Toggle whether tracks are split apart or merged together')\n .setOnclick(() => {\n data_layer.toggleSplitTracks();\n // FIXME: the timeout calls to scale and position (below) cause full ~5 additional re-renders\n // If we can remove these it will greatly speed up re-rendering.\n // The key problem here is that the height is apparently not known in advance and is determined after re-render.\n if (this.scale_timeout) {\n clearTimeout(this.scale_timeout);\n }\n this.scale_timeout = setTimeout(() => {\n this.parent_panel.scaleHeightToData();\n this.parent_plot.positionPanels();\n }, 0);\n this.update();\n });\n return this.update();\n }\n }\n }\n\n\n /**\n * (**extension**) Convert a value \"\"rr,gg,bb\" (if given) to a css-friendly color string: \"rgb(rr,gg,bb)\".\n * This is tailored specifically to the color specification format embraced by the BED file standard.\n * @alias module:LocusZoom_ScaleFunctions~to_rgb\n * @param {Object} parameters This function has no defined configuration options\n * @param {String|null} value The value to convert to rgb\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n function to_rgb(parameters, value) {\n return value ? `rgb(${value})` : null;\n }\n\n const default_layout = {\n start_field: 'start',\n end_field: 'end',\n track_label_field: 'state_name', // Used to label items on the y-axis\n // Used to uniquely identify tracks for coloring. This tends to lead to more stable coloring/sorting\n // than using the label field- eg, state_ids allow us to set global colors across the entire dataset,\n // not just choose unique colors within a particular narrow region. (where changing region might lead to more\n // categories and different colors)\n track_split_field: 'state_id',\n track_split_order: 'DESC',\n track_split_legend_to_y_axis: 2,\n split_tracks: true,\n track_height: 15,\n track_vertical_spacing: 3,\n bounding_box_padding: 2,\n always_hide_legend: false,\n color: '#B8B8B8',\n fill_opacity: 1,\n tooltip_positioning: 'vertical',\n };\n\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n\n /**\n * (**extension**) Implements a data layer that will render interval annotation tracks (intervals must provide start and end values)\n * Each interval (such as from a BED file) will be rendered as a rectangle. All spans can be rendered on the same\n * row, or each (auto-detected) category can be rendered as one row per category.\n *\n * This layer is intended to work with a variety of datasets with special requirements. As such, it has a lot\n * of configuration options devoted to identifying how to fill in missing information (such as color)\n *\n * @alias module:LocusZoom_DataLayers~intervals\n * @see module:LocusZoom_DataLayers~BaseDataLayer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n class LzIntervalsTrack extends BaseLayer {\n /**\n * @param {string} [layout.start_field='start'] The field that defines interval start position\n * @param {string} [layout.end_field='end'] The field that defines interval end position\n * @param {string} [layout.track_label_field='state_name'] Used to label items on the y-axis\n * @param {string} [layout.track_split_field='state_id'] Used to define categories on the y-axis. It is usually most convenient to use\n * the same value for state_field and label_field (eg 1:1 correspondence).\n * @param {*|'DESC'} [layout.track_split_order='DESC'] When in split tracks mode, should categories be shown in\n * the order given, or descending order\n * @param {number} [layout.track_split_legend_to_y_axis=2]\n * @param {boolean} [layout.split_tracks=true] Whether to show tracks as merged (one row) or split (many rows)\n * on initial render.\n * @param {number} [layout.track_height=15] The height of each interval rectangle, in px\n * @param {number} [layout.track_vertical_spacing=3]\n * @param {number} [layout.bounding_box_padding=2]\n * @param {boolean} [layout.always_hide_legend=false] Normally the legend is shown in merged mode and hidden\n * in split mode. For datasets with a very large number of categories, it may make sense to hide the legend at all times.\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#B8B8B8'] The color of each datum rectangle\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1]\n * @param {string} [layout.tooltip_positioning='vertical']\n */\n constructor(layout) {\n LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n this._previous_categories = [];\n this._categories = [];\n }\n\n initialize() {\n super.initialize();\n this._statusnodes_group = this.svg.group.append('g')\n .attr('class', 'lz-data-layer-intervals lz-data-layer-intervals-statusnode');\n this._datanodes_group = this.svg.group.append('g')\n .attr('class', 'lz-data_layer-intervals');\n }\n\n /**\n * Split data into tracks such that anything with a common grouping field is in the same track\n * @param data\n * @return {unknown[]}\n * @private\n */\n _arrangeTrackSplit(data) {\n const {track_split_field} = this.layout;\n const result = {};\n data.forEach((item) => {\n const item_key = item[track_split_field];\n if (!Object.prototype.hasOwnProperty.call(result, item_key)) {\n result[item_key] = [];\n }\n result[item_key].push(item);\n });\n return result;\n }\n\n /**\n * Split data into rows using a simple greedy algorithm such that no two items overlap (share same interval)\n * Assumes that the data are sorted so item1.start always <= item2.start.\n *\n * This function can also simply return all data on a single row. This functionality may become configurable\n * in the future but for now reflects a lack of clarity in the requirements/spec. The code to split\n * overlapping items is present but may not see direct use.\n */\n _arrangeTracksLinear(data, allow_overlap = true) {\n if (allow_overlap) {\n // If overlap is allowed, then all the data can live on a single row\n return [data];\n }\n\n // ASSUMPTION: Data is given to us already sorted by start position to facilitate grouping.\n // We do not sort here because JS \"sort\" is not stable- if there are many intervals that overlap, then we\n // can get different layouts (number/order of rows) on each call to \"render\".\n //\n // At present, we decide how to update the y-axis based on whether current and former number of rows are\n // the same. An unstable sort leads to layout thrashing/too many re-renders. FIXME: don't rely on counts\n const {start_field, end_field} = this.layout;\n\n const grouped_data = [[]]; // Prevent two items from colliding by rendering them to different rows, like genes\n data.forEach((item, index) => {\n for (let i = 0; i < grouped_data.length; i++) {\n // Iterate over all rows of the\n const row_to_test = grouped_data[i];\n const last_item = row_to_test[row_to_test.length - 1];\n // Some programs report open intervals, eg 0-1,1-2,2-3; these points are not considered to overlap (hence the test isn't \"<=\")\n const has_overlap = last_item && (item[start_field] < last_item[end_field]) && (last_item[start_field] < item[end_field]);\n if (!has_overlap) {\n // If there is no overlap, add item to current row, and move on to the next item\n row_to_test.push(item);\n return;\n }\n }\n // If this item would collide on all existing rows, create a new row\n grouped_data.push([item]);\n });\n return grouped_data;\n }\n\n /**\n * Annotate each item with the track number, and return.\n * @param {Object[]}data\n * @private\n * @return [String[], Object[]] Return the categories and the data array\n */\n _assignTracks(data) {\n // Flatten the grouped data.\n const {x_scale} = this.parent;\n const {start_field, end_field, bounding_box_padding, track_height} = this.layout;\n\n const grouped_data = this.layout.split_tracks ? this._arrangeTrackSplit(data) : this._arrangeTracksLinear(data, true);\n const categories = Object.keys(grouped_data);\n if (this.layout.track_split_order === 'DESC') {\n categories.reverse();\n }\n\n categories.forEach((key, row_index) => {\n const row = grouped_data[key];\n row.forEach((item) => {\n item[XCS] = x_scale(item[start_field]);\n item[XCE] = x_scale(item[end_field]);\n item[YCS] = row_index * this.getTrackHeight() + bounding_box_padding;\n item[YCE] = item[YCS] + track_height;\n // Store the row ID, so that clicking on a point can find the right status node (big highlight box)\n item.track = row_index;\n });\n });\n // We're mutating elements of the original data array as a side effect: the return value here is\n // interchangeable with `this.data` for subsequent usages\n // TODO: Can replace this with array.flat once polyfill support improves\n return [categories, Object.values(grouped_data).reduce((acc, val) => acc.concat(val), [])];\n }\n\n /**\n * When we are in \"split tracks mode\", it's convenient to wrap all individual annotations with a shared\n * highlight box that wraps everything on that row.\n *\n * This is done automatically by the \"setElementStatus\" code, if this function returns a non-null value\n *\n * To define shared highlighting on the track split field define the status node id override\n * to generate an ID common to the track when we're actively splitting data out to separate tracks\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n if (this.layout.split_tracks) {\n // Data nodes are bound to data objects, but the \"status_nodes\" selection is bound to numeric row IDs\n const track = typeof element === 'object' ? element.track : element;\n const base = `${this.getBaseId()}-statusnode-${track}`;\n return base.replace(/[^\\w]/g, '_');\n }\n // In merged tracks mode, there is no separate status node\n return null;\n }\n\n // Helper function to sum layout values to derive total height for a single interval track\n getTrackHeight() {\n return this.layout.track_height\n + this.layout.track_vertical_spacing\n + (2 * this.layout.bounding_box_padding);\n }\n\n // Modify the layout as necessary to ensure that appropriate color, label, and legend options are available\n // Even when not displayed, the legend is used to generate the y-axis ticks\n _applyLayoutOptions() {\n const self = this;\n const base_layout = this._base_layout;\n const render_layout = this.layout;\n const base_color_scale = base_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'categorical_bin';\n });\n const color_scale = render_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'categorical_bin';\n });\n if (!base_color_scale) {\n // This can be a placeholder (empty categories & values), but it needs to be there\n throw new Error('Interval tracks must define a `categorical_bin` color scale');\n }\n\n const has_colors = base_color_scale.parameters.categories.length && base_color_scale.parameters.values.length;\n const has_legend = base_layout.legend && base_layout.legend.length;\n\n if (!!has_colors ^ !!has_legend) {\n // Don't allow color OR legend to be set manually. It must be both, or neither.\n throw new Error('To use a manually specified color scheme, both color and legend options must be set.');\n }\n\n // Harvest any information about an explicit color field that should be considered when generating colors\n const rgb_option = base_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'to_rgb';\n });\n const rgb_field = rgb_option && rgb_option.field;\n\n // Auto-generate legend based on data\n const known_categories = this._generateCategoriesFromData(this.data, rgb_field); // [id, label, itemRgb] items\n\n if (!has_colors && !has_legend) {\n // If no color scheme pre-defined, then make a color scheme that is appropriate and apply to the plot\n // The legend must match the color scheme. If we generate one, then we must generate both.\n\n const colors = this._makeColorScheme(known_categories);\n color_scale.parameters.categories = known_categories.map(function (item) {\n return item[0];\n });\n color_scale.parameters.values = colors;\n\n this.layout.legend = known_categories.map(function (pair, index) {\n const id = pair[0];\n const label = pair[1];\n const item_color = color_scale.parameters.values[index];\n const item = { shape: 'rect', width: 9, label: label, color: item_color };\n item[self.layout.track_split_field] = id;\n return item;\n });\n }\n }\n\n // Implement the main render function\n render() {\n //// Autogenerate layout options if not provided\n this._applyLayoutOptions();\n\n // Determine the appropriate layout for tracks. Store the previous categories (y axis ticks) to decide\n // whether the axis needs to be re-rendered.\n this._previous_categories = this._categories;\n const [categories, assigned_data] = this._assignTracks(this.data);\n this._categories = categories;\n // Update the legend axis if the number of ticks changed\n const labels_changed = !categories.every( (item, index) => item === this._previous_categories[index]);\n if (labels_changed) {\n this.updateSplitTrackAxis(categories);\n return;\n }\n\n // Apply filters to only render a specified set of points. Hidden fields will still be given space to render, but not shown.\n const track_data = this._applyFilters(assigned_data);\n\n // Clear before every render so that, eg, highlighting doesn't persist if we load a region with different\n // categories (row 2 might be a different category and it's confusing if the row stays highlighted but changes meaning)\n // Highlighting will automatically get added back if it actually makes sense, courtesy of setElementStatus,\n // if a selected item is still in view after the new region loads.\n this._statusnodes_group.selectAll('rect')\n .remove();\n\n // Reselect in order to add new data\n const status_nodes = this._statusnodes_group.selectAll('rect')\n .data(d3.range(categories.length));\n\n if (this.layout.split_tracks) {\n // Status nodes: a big highlight box around all items of the same type. Used in split tracks mode,\n // because everything on the same row is the same category and a group makes sense\n // There are no status nodes in merged mode, because the same row contains many kinds of things\n\n // Status nodes are 1 per row, so \"data\" can just be a dummy list of possible row IDs\n // Each status node is a box that runs the length of the panel and receives a special \"colored box\" css\n // style when selected\n const height = this.getTrackHeight();\n status_nodes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared')\n .attr('rx', this.layout.bounding_box_padding)\n .attr('ry', this.layout.bounding_box_padding)\n .merge(status_nodes)\n .attr('id', (d) => this.getElementStatusNodeId(d))\n .attr('x', 0)\n .attr('y', (d) => (d * height))\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', Math.max(height - this.layout.track_vertical_spacing, 1));\n }\n status_nodes.exit()\n .remove();\n\n // Draw rectangles for the data (intervals)\n const data_nodes = this._datanodes_group.selectAll('rect')\n .data(track_data, (d) => d[this.layout.id_field]);\n\n data_nodes.enter()\n .append('rect')\n .merge(data_nodes)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => d[XCS])\n .attr('y', (d) => d[YCS])\n .attr('width', (d) => Math.max(d[XCE] - d[XCS], 1))\n .attr('height', this.layout.track_height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n data_nodes.exit()\n .remove();\n\n this._datanodes_group\n .call(this.applyBehaviors.bind(this));\n\n // The intervals track allows legends to be dynamically generated, in which case space can only be\n // allocated after the panel has been rendered.\n if (this.parent && this.parent.legend) {\n this.parent.legend.render();\n }\n }\n\n _getTooltipPosition(tooltip) {\n return {\n x_min: tooltip.data[XCS],\n x_max: tooltip.data[XCE],\n y_min: tooltip.data[YCS],\n y_max: tooltip.data[YCE],\n };\n }\n\n // Redraw split track axis or hide it, and show/hide the legend, as determined\n // by current layout parameters and data\n updateSplitTrackAxis(categories) {\n const legend_axis = this.layout.track_split_legend_to_y_axis ? `y${this.layout.track_split_legend_to_y_axis}` : false;\n if (this.layout.split_tracks) {\n const tracks = +categories.length || 0;\n const track_height = +this.layout.track_height || 0;\n const track_spacing = 2 * (+this.layout.bounding_box_padding || 0) + (+this.layout.track_vertical_spacing || 0);\n const target_height = (tracks * track_height) + ((tracks - 1) * track_spacing);\n this.parent.scaleHeightToData(target_height);\n if (legend_axis && this.parent.legend) {\n this.parent.legend.hide();\n this.parent.layout.axes[legend_axis] = {\n render: true,\n ticks: [],\n range: {\n start: (target_height - (this.layout.track_height / 2)),\n end: (this.layout.track_height / 2),\n },\n };\n // There is a very tight coupling between the display directives: each legend item must identify a key\n // field for unique tracks. (Typically this is `state_id`, the same key field used to assign unique colors)\n // The list of unique keys corresponds to the order along the y-axis\n this.layout.legend.forEach((element) => {\n const key = element[this.layout.track_split_field];\n let track = categories.findIndex((item) => item === key);\n if (track !== -1) {\n if (this.layout.track_split_order === 'DESC') {\n track = Math.abs(track - tracks - 1);\n }\n this.parent.layout.axes[legend_axis].ticks.push({\n y: track - 1,\n text: element.label,\n });\n }\n });\n this.layout.y_axis = {\n axis: this.layout.track_split_legend_to_y_axis,\n floor: 1,\n ceiling: tracks,\n };\n }\n // This will trigger a re-render\n this.parent_plot.positionPanels();\n } else {\n if (legend_axis && this.parent.legend) {\n if (!this.layout.always_hide_legend) {\n this.parent.legend.show();\n }\n this.parent.layout.axes[legend_axis] = { render: false };\n this.parent.render();\n }\n }\n return this;\n }\n\n // Method to not only toggle the split tracks boolean but also update\n // necessary display values to animate a complete merge/split\n toggleSplitTracks() {\n this.layout.split_tracks = !this.layout.split_tracks;\n if (this.parent.legend && !this.layout.always_hide_legend) {\n this.parent.layout.margin.bottom = 5 + (this.layout.split_tracks ? 0 : this.parent.legend.layout.height + 5);\n }\n this.render();\n return this;\n }\n\n // Choose an appropriate color scheme based on the number of items in the track, and whether or not we are\n // using explicitly provided itemRgb information\n _makeColorScheme(category_info) {\n // If at least one element has an explicit itemRgb, assume the entire dataset has colors. BED intervals require rgb triplets,so assume that colors will always be \"r,g,b\" format.\n const has_explicit_colors = category_info.find((item) => item[2]);\n if (has_explicit_colors) {\n return category_info.map((item) => to_rgb({}, item[2]));\n }\n\n // Use a set of color schemes for common 15, 18, or 25 state models, as specified from:\n // https://egg2.wustl.edu/roadmap/web_portal/chr_state_learning.html\n // These are actually reversed so that dim colors come first, on the premise that usually these are the\n // most common states\n const n_categories = category_info.length;\n if (n_categories <= 15) {\n return ['rgb(212,212,212)', 'rgb(192,192,192)', 'rgb(128,128,128)', 'rgb(189,183,107)', 'rgb(233,150,122)', 'rgb(205,92,92)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,0)', 'rgb(194,225,5)', 'rgb(0,100,0)', 'rgb(0,128,0)', 'rgb(50,205,50)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n } else if (n_categories <= 18) {\n return ['rgb(212,212,212)', 'rgb(192,192,192)', 'rgb(128,128,128)', 'rgb(189,183,107)', 'rgb(205,92,92)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,0)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(0,100,0)', 'rgb(0,128,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n } else {\n // If there are more than 25 categories, the interval layer will fall back to the 'null value' option\n return ['rgb(212,212,212)', 'rgb(128,128,128)', 'rgb(112,48,160)', 'rgb(230,184,183)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,102)', 'rgb(255,255,0)', 'rgb(255,255,0)', 'rgb(255,255,0)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(0,150,0)', 'rgb(0,128,0)', 'rgb(0,128,0)', 'rgb(0,128,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n }\n }\n\n /**\n * Find all of the unique tracks (a combination of name and ID information)\n * @param {Object} data\n * @param {String} [rgb_field] A field that contains an RGB value. Aimed at BED files with an itemRgb column\n * @private\n * @returns {Array} All [unique_id, label, color] pairs in data. The unique_id is the thing used to define groupings\n * most unambiguously.\n */\n _generateCategoriesFromData(data, rgb_field) {\n const self = this;\n // Use the hard-coded legend if available (ignoring any mods on re-render)\n const legend = this._base_layout.legend;\n if (legend && legend.length) {\n return legend.map((item) => [item[this.layout.track_split_field], item.label, item.color]);\n }\n\n // Generate options from data, if no preset legend exists\n const unique_ids = {}; // make categories unique\n const categories = [];\n\n data.forEach((item) => {\n const id = item[self.layout.track_split_field];\n if (!Object.prototype.hasOwnProperty.call(unique_ids, id)) {\n unique_ids[id] = null;\n // If rgbfield is null, then the last entry is undefined/null as well\n categories.push([id, item[this.layout.track_label_field], item[rgb_field]]);\n }\n });\n return categories;\n }\n }\n\n /**\n * (**extension**) A basic tooltip with information to be shown over an intervals datum\n * @alias module:LocusZoom_Layouts~standard_intervals\n * @type tooltip\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_tooltip_layout = {\n closable: false,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{intervals:state_name|htmlescape}}
{{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}',\n };\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for chromHMM output,\n * in which various states are assigned numeric state IDs and (<= as many) text state names.\n *\n * This layout is deprecated; most usages would be better served by the bed_intervals_layer layout instead.\n * @alias module:LocusZoom_Layouts~intervals_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_layer_layout = {\n namespace: { 'intervals': 'intervals' },\n id: 'intervals',\n type: 'intervals',\n tag: 'intervals',\n id_field: '{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}',\n start_field: 'intervals:start',\n end_field: 'intervals:end',\n track_split_field: 'intervals:state_name',\n track_label_field: 'intervals:state_name',\n split_tracks: false,\n always_hide_legend: true,\n color: [\n {\n // If present, an explicit color field will override any other option (and be used to auto-generate legend)\n field: 'intervals:itemRgb',\n scale_function: 'to_rgb',\n },\n {\n // TODO: Consider changing this to stable_choice in the future, for more stable coloring\n field: 'intervals:state_name',\n scale_function: 'categorical_bin',\n parameters: {\n // Placeholder. Empty categories and values will automatically be filled in when new data loads.\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n },\n ],\n legend: [], // Placeholder; auto-filled when data loads.\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: intervals_tooltip_layout,\n };\n\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for standard BED3+ files and the field names emitted by the LzParsers extension.\n * @alias module:LocusZoom_Layouts~bed_intervals_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const bed_intervals_layer_layout = LocusZoom.Layouts.merge({\n id_field: '{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}',\n start_field: 'intervals:chromStart',\n end_field: 'intervals:chromEnd',\n track_split_field: 'intervals:name',\n track_label_field: 'intervals:name',\n split_tracks: true,\n always_hide_legend: false,\n color: [\n {\n // If present, an explicit color field will override any other option (and be used to auto-generate legend)\n field: 'intervals:itemRgb',\n scale_function: 'to_rgb',\n },\n {\n // TODO: Consider changing this to stable_choice in the future, for more stable coloring\n field: 'intervals:name',\n scale_function: 'categorical_bin',\n parameters: {\n // Placeholder. Empty categories and values will automatically be filled in when new data loads.\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n },\n ],\n tooltip: LocusZoom.Layouts.merge({\n html: `Group: {{intervals:name|htmlescape}}
\nRegion: {{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}}\n{{#if intervals:score}}
\nScore: {{intervals:score|htmlescape}}{{/if}}`,\n }, intervals_tooltip_layout),\n }, intervals_layer_layout);\n\n\n /**\n * (**extension**) A panel containing an intervals data layer, eg for BED tracks. This is a legacy layout whose field names were specific to one partner site.\n * @alias module:LocusZoom_Layouts~intervals\n * @type panel\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_panel_layout = {\n id: 'intervals',\n tag: 'intervals',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 150, bottom: 5, left: 50 },\n toolbar: (function () {\n const l = LocusZoom.Layouts.get('toolbar', 'standard_panel');\n l.widgets.push({\n type: 'toggle_split_tracks',\n data_layer_id: 'intervals',\n position: 'right',\n });\n return l;\n })(),\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n legend: {\n hidden: true,\n orientation: 'horizontal',\n origin: { x: 50, y: 0 },\n pad_from_bottom: 5,\n },\n data_layers: [intervals_layer_layout],\n };\n\n /**\n * (**extension**) A panel containing an intervals data layer, eg for BED tracks. These field names match those returned by the LzParsers extension.\n * @alias module:LocusZoom_Layouts~bed_intervals\n * @type panel\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const bed_intervals_panel_layout = LocusZoom.Layouts.merge({\n // Normal BED tracks show the panel legend in collapsed mode!\n min_height: 120,\n height: 120,\n data_layers: [bed_intervals_layer_layout],\n }, intervals_panel_layout);\n\n /**\n * (**extension**) A plot layout that shows association summary statistics, genes, and interval data. This example assumes\n * chromHMM data. (see panel layout) Few people will use the full intervals plot layout directly outside of an example.\n * @alias module:LocusZoom_Layouts~interval_association\n * @type plot\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_plot_layout = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n LocusZoom.Layouts.get('panel', 'association'),\n LocusZoom.Layouts.merge({ min_height: 120, height: 120 }, intervals_panel_layout),\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n\n LocusZoom.Adapters.add('IntervalLZ', IntervalLZ);\n LocusZoom.DataLayers.add('intervals', LzIntervalsTrack);\n\n LocusZoom.Layouts.add('tooltip', 'standard_intervals', intervals_tooltip_layout);\n LocusZoom.Layouts.add('data_layer', 'intervals', intervals_layer_layout);\n LocusZoom.Layouts.add('data_layer', 'bed_intervals', bed_intervals_layer_layout);\n LocusZoom.Layouts.add('panel', 'intervals', intervals_panel_layout);\n LocusZoom.Layouts.add('panel', 'bed_intervals', bed_intervals_panel_layout);\n LocusZoom.Layouts.add('plot', 'interval_association', intervals_plot_layout);\n\n LocusZoom.ScaleFunctions.add('to_rgb', to_rgb);\n\n LocusZoom.Widgets.add('toggle_split_tracks', ToggleSplitTracks);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-parsers.min.js b/dist/ext/lz-parsers.min.js new file mode 100644 index 00000000..74b44fb1 --- /dev/null +++ b/dist/ext/lz-parsers.min.js @@ -0,0 +1,3 @@ +/*! Locuszoom 0.14.0-beta.1 */ +var LzParsers;(()=>{"use strict";var e={d:(r,t)=>{for(var l in t)e.o(t,l)&&!e.o(r,l)&&Object.defineProperty(r,l,{enumerable:!0,get:t[l]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r)},r={};e.d(r,{default:()=>d});const t=new Set(["",".","NA","N/A","n/a","nan","-nan","NaN","-NaN","null","NULL","None",null]),l=/([\d.-]+)([\sxeE]*)([0-9-]*)/;function n(e){return Number.isInteger(e)}function o(e,r=t,l=null){return e.map((e=>r.has(e)?l:e))}function a(e){return e.replace(/^chr/g,"").toUpperCase()}function s(e,r=!1){if(null===e)return e;const t=+e;if(r)return t;if(t<0||t>1)throw new Error("p value is not in the allowed range");if(0===t){if("0"===e)return 1/0;let[,r,,t]=e.match(l);return r=+r,t=""!==t?+t:0,0===r?1/0:-(Math.log10(+r)+ +t)}return-Math.log10(t)}function u(e){return null==e||"."===e?null:e}function i(e){return(e=u(e))?+e:null}const c=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function f(e,r=!1){const t=e&&e.match(c);if(t)return t.slice(1);if(r)return null;throw new Error(`Could not understand marker format for ${e}. Should be of format chr:pos or chr:pos_ref/alt`)}function p(e){const r=f(e);if(!r)throw new Error(`Unable to normalize marker format for variant: ${e}`);const[t,l,n,o]=r;let a=`${t}:${l}`;return n&&o&&(a+=`_${n}/${o}`),a}function _(e,r){const t=Array(r.length+1).fill(null).map((()=>Array(e.length+1).fill(null)));for(let r=0;r<=e.length;r+=1)t[0][r]=r;for(let e=0;e<=r.length;e+=1)t[e][0]=e;for(let l=1;l<=r.length;l+=1)for(let n=1;n<=e.length;n+=1){const o=e[n-1]===r[l-1]?0:1;t[l][n]=Math.min(t[l][n-1]+1,t[l-1][n]+1,t[l-1][n-1]+o)}return t[r.length][e.length]}function m(e,r,t=2){let l=t+1,n=null;for(let t=0;t_(o,e))));ae.variant1===r.ld_refvar))}}e.Adapters.add("UserTabixLD",l)}}"undefined"!=typeof LocusZoom&&LocusZoom.use(h);const d={install:h,makeBed12Parser:function({normalize:e=!0}={}){return r=>{const t=r.trim().split("\t");let[l,n,o,s,c,f,p,_,m,h,d,v]=t;if(!(l&&n&&o))throw new Error("Sample data must provide all required BED columns");if(f=u(f),e&&(l=a(l),n=+n+1,o=+o,c=i(c),p=i(p),_=i(_),m=u(m),h=i(h),d=u(d),d=d?d.replace(/,$/,"").split(",").map((e=>+e)):null,v=u(v),v=v?v.replace(/,$/,"").split(",").map((e=>+e+1)):null,d&&v&&h&&(d.length!==h||v.length!==h)))throw new Error("Block size and start information should provide the same number of items as in blockCount");return{chrom:l,chromStart:n,chromEnd:o,name:s,score:c,strand:f,thickStart:p,thickEnd:_,itemRgb:m,blockCount:h,blockSizes:d,blockStarts:v}}},makeGWASParser:function({marker_col:e,chrom_col:r,pos_col:l,ref_col:o,alt_col:u,pvalue_col:i,is_neg_log_pvalue:c=!1,rsid_col:p,beta_col:_,stderr_beta_col:m,allele_freq_col:h,allele_count_col:d,n_samples_col:v,is_alt_effect:g=!0,delimiter:w="\t"}){if(n(e)&&n(r)&&n(l))throw new Error("Must specify either marker OR chr + pos");if(!(n(e)||n(r)&&n(l)))throw new Error("Must specify how to locate marker");if(n(d)&&n(h))throw new Error("Allele count and frequency options are mutually exclusive");if(n(d)&&!n(v))throw new Error("To calculate allele frequency from counts, you must also provide n_samples");return b=>{const k=b.split(w);let y,E,q,A,N,$,S,L=null,z=null,P=null,C=null;if(n(e))[y,E,q,A]=f(k[e-1],!1);else{if(!n(r)||!n(l))throw new Error("Must specify all fields required to identify the variant");y=k[r-1],E=k[l-1]}if(y=a(y),y.startsWith("RS"))throw new Error(`Invalid chromosome specified: value "${y}" is an rsID`);n(o)&&(q=k[o-1]),n(u)&&(A=k[u-1]),n(p)&&(L=k[p-1]),t.has(q)&&(q=null),t.has(A)&&(A=null),t.has(L)?L=null:L&&(L=L.toLowerCase(),L.startsWith("rs")||(L=`rs${L}`));const O=s(k[i-1],c);q=q||null,A=A||null,n(h)&&(N=k[h-1]),n(d)&&($=k[d-1],S=k[v-1]),n(_)&&(z=k[_-1],z=t.has(z)?null:+z),n(m)&&(P=k[m-1],P=t.has(P)?null:+P),(h||d)&&(C=function({freq:e,allele_count:r,n_samples:l,is_alt_effect:n=!0}){if(void 0!==e&&void 0!==r)throw new Error("Frequency and allele count options are mutually exclusive");let o;if(void 0===e&&(t.has(r)||t.has(l)))return null;if(void 0===e&&void 0!==r)o=+r/+l/2;else{if(t.has(e))return null;o=+e}if(o<0||o>1)throw new Error("Allele frequency is not in the allowed range");return n?o:1-o}({freq:N,allele_count:$,n_samples:S,is_alt_effect:g}));const R=q&&A?`_${q}/${A}`:"";return{chromosome:y,position:+E,ref_allele:q?q.toUpperCase():null,alt_allele:A?A.toUpperCase():null,variant:`${y}:${E}${R}`,rsid:L,log_pvalue:O,beta:z,stderr_beta:P,alt_allele_freq:C}}},guessGWAS:function(e,r,t=1){const l=e.map((e=>e?e.toLowerCase():e));l[0].replace("/^#+/","");const n=function(e,r){let t;const l=(e,r,l)=>{const n=o(r.map((r=>r[e])));try{t=n.map((e=>s(e,l)))}catch(e){return!1}return t.every((e=>!Number.isNaN(e)))},n=m(["neg_log_pvalue","log_pvalue","log_pval","logpvalue"],e),a=m(["pvalue","p.value","p-value","pval","p_score","p","p_value"],e);return null!==n&&l(n,r,!0)?{pvalue_col:n+1,is_neg_log_pvalue:!0}:a&&l(a,r,!1)?{pvalue_col:a+1,is_neg_log_pvalue:!1}:null}(l,r);if(!n)return null;l[n.pvalue_col-1]=null;const a=function(e,r){const t=r[0];let l=m(["snpid","marker","markerid","snpmarker","chr:position"],e);if(null!==l&&f(t[l],!0))return l+=1,{marker_col:l};const n=e.slice(),o=[["chrom_col",["chrom","chr","chromosome"],!0],["pos_col",["position","pos","begin","beg","bp","end","ps","base_pair_location"],!0],["ref_col",["A1","ref","reference","allele0","allele1"],!1],["alt_col",["A2","alt","alternate","allele1","allele2"],!1]],a={};for(let e=0;e{l[a[e]]=null}));const u=function(e,r){function t(e,r){const t=o(r.map((r=>r[e])));let l;try{l=t.filter((e=>null!==e)).map((e=>+e))}catch(e){return!1}return l.every((e=>!Number.isNaN(e)))}const l=m(["beta","effect_size","alt_effsize","effect"],e,0),n=m(["stderr_beta","stderr","sebeta","effect_size_sd","se","standard_error"],e,0),a={};return null!==l&&t(l,r)&&(a.beta_col=l+1),null!==n&&t(n,r)&&(a.stderr_beta_col=n+1),a}(l,r);return n&&a?Object.assign({},n,a,u||{}):null},makePlinkLdParser:function({normalize:e=!0}={}){return r=>{let[t,l,n,o,s,u,i]=r.trim().split("\t");return e&&(t=a(t),o=a(o),n=p(n),u=p(u),l=+l,s=+s,i=+i),{chromosome1:t,position1:l,variant1:n,chromosome2:o,position2:s,variant2:u,correlation:i}}}};LzParsers=r.default})(); +//# sourceMappingURL=lz-parsers.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-parsers.min.js.map b/dist/ext/lz-parsers.min.js.map new file mode 100644 index 00000000..b443db68 --- /dev/null +++ b/dist/ext/lz-parsers.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-parsers/utils.js","webpack://[name]/./esm/ext/lz-parsers/bed.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/ext/lz-parsers/gwas/sniffers.js","webpack://[name]/./esm/ext/lz-parsers/index.js","webpack://[name]/./esm/ext/lz-parsers/gwas/parsers.js","webpack://[name]/./esm/ext/lz-parsers/ld.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Set","REGEX_PVAL","has","num","Number","isInteger","missingToNull","values","nulls","placeholder","map","v","normalizeChr","chromosome","replace","toUpperCase","parsePvalToLog","value","is_neg_log","val","Error","Infinity","base","exponent","match","Math","log10","_bedMissing","_hasNum","REGEX_MARKER","parseMarker","test","slice","normalizeMarker","variant","chrom","pos","ref","alt","normalized","levenshtein","a","b","distanceMatrix","Array","length","fill","i","j","indicator","min","findColumn","column_synonyms","header_names","threshold","best_score","best_match","header","score","s","install","LocusZoom","Adapters","TabixUrlSource","LDServer","UserTabixLD","config","limit_fields","super","state","assoc_data","_buildRequestOptions","arguments","ld_refvar","__find_ld_refvar","_skip_request","options","Promise","resolve","_performRequest","records","filter","item","add","use","makeBed12Parser","normalize","line","tokens","trim","split","chromStart","chromEnd","name","strand","thickStart","thickEnd","itemRgb","blockCount","blockSizes","blockStarts","marker_col","chrom_col","pos_col","ref_col","alt_col","pvalue_col","is_neg_log_pvalue","rsid_col","beta_col","stderr_beta_col","allele_freq_col","allele_count_col","n_samples_col","is_alt_effect","delimiter","fields","chr","freq","allele_count","n_samples","rsid","beta","stderr_beta","alt_allele_freq","startsWith","toLowerCase","log_pval","undefined","result","parseAlleleFrequency","ref_alt","position","ref_allele","alt_allele","log_pvalue","header_row","data_rows","offset","headers","pval_config","ps","validateP","col","data","is_log","cleaned_vals","row","p","e","every","isNaN","log_p_col","p_col","getPvalColumn","position_config","first_row","headers_marked","find","col_name","choices","is_required","getChromPosRefAltColumns","keys","forEach","beta_config","validate_numeric","nums","ret","getEffectSizeColumns","assign","chromosome1","position1","variant1","chromosome2","position2","variant2","correlation"],"mappings":"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","file":"ext/lz-parsers.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Constant values used by GWAS parser\n */\n\n/**\n * @private\n */\nconst MISSING_VALUES = new Set(['', '.', 'NA', 'N/A', 'n/a', 'nan', '-nan', 'NaN', '-NaN', 'null', 'NULL', 'None', null]);\n\n/**\n * @private\n */\nconst REGEX_PVAL = /([\\d.-]+)([\\sxeE]*)([0-9-]*)/;\n\n\n/**\n * Utility helper that checks for the presence of a numeric value (incl 0),\n * eg \"has column specified\"\n * @private\n * @param num\n * @returns {boolean}\n */\nfunction has(num) {\n return Number.isInteger(num);\n}\n\n/**\n * Convert all missing values to a standardized input form\n * Useful for columns like pvalue, where a missing value explicitly allowed\n * @private\n */\nfunction missingToNull(values, nulls = MISSING_VALUES, placeholder = null) {\n // TODO Make this operate on a single value; cache for efficiency?\n return values.map((v) => (nulls.has(v) ? placeholder : v));\n}\n\n/**\n * Normalize chromosome representations\n * @private\n * @param {String} chromosome\n */\nfunction normalizeChr(chromosome) {\n return chromosome.replace(/^chr/g, '').toUpperCase();\n}\n\n/**\n * Parse (and validate) a given number, and return the -log10 pvalue.\n * @private\n * @param value\n * @param {boolean} is_neg_log\n * @returns {number|null} The -log10 pvalue\n */\nfunction parsePvalToLog(value, is_neg_log = false) {\n // TODO: In future, generalize this for other values prone to underflow\n if (value === null) {\n return value;\n }\n const val = +value;\n if (is_neg_log) { // Take as is\n return val;\n }\n // Regular pvalue: validate and convert\n if (val < 0 || val > 1) {\n throw new Error('p value is not in the allowed range');\n }\n // 0-values are explicitly allowed and will convert to infinity by design, as they often\n // indicate underflow errors in the input data.\n if (val === 0) {\n // Determine whether underflow is due to the source data, or value conversion\n if (value === '0') {\n // The source data is bad, so insert an obvious placeholder value\n return Infinity;\n }\n // h/t @welchr: aggressively turn the underflowing string value into -log10 via regex\n // Only do this if absolutely necessary, because it is a performance hit\n\n let [, base, , exponent] = value.match(REGEX_PVAL);\n base = +base;\n\n if (exponent !== '') {\n exponent = +exponent;\n } else {\n exponent = 0;\n }\n if (base === 0) {\n return Infinity;\n }\n return -(Math.log10(+base) + +exponent);\n }\n return -Math.log10(val);\n}\n\n/**\n * @private\n */\nfunction parseAlleleFrequency({ freq, allele_count, n_samples, is_alt_effect = true }) {\n if (freq !== undefined && allele_count !== undefined) {\n throw new Error('Frequency and allele count options are mutually exclusive');\n }\n\n let result;\n if (freq === undefined && (MISSING_VALUES.has(allele_count) || MISSING_VALUES.has(n_samples))) {\n // Allele count parsing\n return null;\n }\n if (freq === undefined && allele_count !== undefined) {\n result = +allele_count / +n_samples / 2;\n } else if (MISSING_VALUES.has(freq)) { // Frequency-based parsing\n return null;\n } else {\n result = +freq;\n }\n\n // No matter how the frequency is specified, this stuff is always done\n if (result < 0 || result > 1) {\n throw new Error('Allele frequency is not in the allowed range');\n }\n if (!is_alt_effect) { // Orient the frequency to the alt allele\n return 1 - result;\n }\n return result;\n}\n\nexport {\n MISSING_VALUES,\n missingToNull as _missingToNull,\n has,\n normalizeChr,\n // Exports for unit testing\n parseAlleleFrequency,\n parsePvalToLog,\n};\n","/**\n * Parse BED-family files, which have 3-12 columns\n * https://genome.ucsc.edu/FAQ/FAQformat.html#format1\n */\n\nimport {normalizeChr} from './utils';\n\n/**\n * @private\n */\nfunction _bedMissing(value) {\n // BED files specify . as the missing/ null value character\n if (value === null || value === undefined || value === '.') {\n return null;\n }\n return value;\n}\n\n/**\n * @private\n */\nfunction _hasNum(value) {\n // Return a number, or null if value marked as missing\n value = _bedMissing(value);\n return value ? +value : null;\n}\n\n/**\n * Parse a BED file, according to the widely used UCSC (quasi-)specification\n *\n * NOTE: This original version is aimed at tabix region queries, and carries an implicit assumption that data is the\n * only thing that will be parsed. It makes no attempt to identify or handle header rows / metadata fields.\n *\n * @function\n * @alias module:ext/lz-parsers~makeBed12Parser\n * @param {object} options\n * @param {Boolean} options.normalize Whether to normalize the output to the format expected by LocusZoom (eg type coercion\n * for numbers, removing chr chromosome prefixes, and using 1-based and inclusive coordinates instead of 0-based disjoint intervals)\n * @return function A configured parser function that runs on one line of text from an input file\n */\nfunction makeBed12Parser({normalize = true} = {}) {\n /*\n * @param {String} line The line of text to be parsed\n */\n return (line) => {\n const tokens = line.trim().split('\\t');\n // The BED file format has 12 standardized columns. 3 are required and 9 are optional. At present, we will not\n // attempt to parse any remaining tokens, or nonstandard files that reuse columns with a different meaning.\n // https://en.wikipedia.org/wiki/BED_(file_format)\n let [\n chrom,\n chromStart,\n chromEnd,\n name,\n score,\n strand,\n thickStart,\n thickEnd,\n itemRgb,\n blockCount,\n blockSizes,\n blockStarts,\n ] = tokens;\n\n if (!(chrom && chromStart && chromEnd)) {\n throw new Error('Sample data must provide all required BED columns');\n }\n\n strand = _bedMissing(strand);\n\n if (normalize) {\n // Mandatory fields\n chrom = normalizeChr(chrom);\n chromStart = +chromStart + 1; // BED is 0 based start, but LZ plots start at 1\n chromEnd = +chromEnd; // 0-based positions, intervals exclude end position\n\n // Optional fields, require checking for blanks\n score = _hasNum(score);\n thickStart = _hasNum(thickStart);\n thickEnd = _hasNum(thickEnd);\n\n itemRgb = _bedMissing(itemRgb);\n\n // LocusZoom doesn't use these fields for rendering. Parsing below is theoretical/best-effort.\n blockCount = _hasNum(blockCount);\n\n blockSizes = _bedMissing(blockSizes);\n blockSizes = !blockSizes ? null : blockSizes.replace(/,$/, '').split(',').map((value) => +value); // Comma separated list of sizes -> array of integers\n\n blockStarts = _bedMissing(blockStarts);\n blockStarts = !blockStarts ? null : blockStarts.replace(/,$/, '').split(',').map((value) => +value + 1); // Comma separated list of sizes -> array of integers (start positions)\n\n if (blockSizes && blockStarts && blockCount && (blockSizes.length !== blockCount || blockStarts.length !== blockCount)) {\n throw new Error('Block size and start information should provide the same number of items as in blockCount');\n }\n }\n return {\n chrom,\n chromStart,\n chromEnd,\n name,\n score,\n strand,\n thickStart,\n thickEnd,\n itemRgb,\n blockCount,\n blockSizes,\n blockStarts,\n };\n };\n}\n\nexport { makeBed12Parser };\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Sniffers: auto detect file format and parsing options for GWAS files.\n */\n\nimport { parseMarker } from '../../../helpers/parse';\nimport { MISSING_VALUES, parsePvalToLog, _missingToNull } from '../utils';\n\n/**\n * @private\n */\nfunction isNumeric(val) {\n // Check whether an unparsed string is a numeric value\"\n if (MISSING_VALUES.has(val)) {\n return true;\n }\n return !Number.isNaN(+val);\n}\n\nfunction isHeader(row, { comment_char = '#', delimiter = '\\t' } = {}) {\n // This assumes two basic rules: the line is not a comment, and gwas data is more likely\n // to be numeric than headers\n return row.startsWith(comment_char) || row.split(delimiter).every((item) => !isNumeric(item));\n}\n\n/**\n * Compute the levenshtein distance between two strings. Useful for finding the single best column\n * name that matches a given rule.\n * @private\n */\nfunction levenshtein(a, b) { // https://github.com/trekhleb/javascript-algorithms\n // Create empty edit distance matrix for all possible modifications of\n // substrings of a to substrings of b.\n const distanceMatrix = Array(b.length + 1)\n .fill(null)\n .map(() => Array(a.length + 1)\n .fill(null));\n\n // Fill the first row of the matrix.\n // If this is first row then we're transforming empty string to a.\n // In this case the number of transformations equals to size of a substring.\n for (let i = 0; i <= a.length; i += 1) {\n distanceMatrix[0][i] = i;\n }\n\n // Fill the first column of the matrix.\n // If this is first column then we're transforming empty string to b.\n // In this case the number of transformations equals to size of b substring.\n for (let j = 0; j <= b.length; j += 1) {\n distanceMatrix[j][0] = j;\n }\n\n for (let j = 1; j <= b.length; j += 1) {\n for (let i = 1; i <= a.length; i += 1) {\n const indicator = a[i - 1] === b[j - 1] ? 0 : 1;\n distanceMatrix[j][i] = Math.min(\n distanceMatrix[j][i - 1] + 1, // deletion\n distanceMatrix[j - 1][i] + 1, // insertion\n distanceMatrix[j - 1][i - 1] + indicator // substitution\n );\n }\n }\n return distanceMatrix[b.length][a.length];\n}\n\n/**\n * Return the index of the first column name that meets acceptance criteria\n * @private\n * @param {String[]} column_synonyms\n * @param {String[]}header_names\n * @param {Number} threshold Tolerance for fuzzy matching (# edits)\n * @return {Number|null} Index of the best matching column, or null if no match possible\n */\nfunction findColumn(column_synonyms, header_names, threshold = 2) {\n // Find the column name that best matches\n let best_score = threshold + 1;\n let best_match = null;\n for (let i = 0; i < header_names.length; i++) {\n const header = header_names[i];\n if (header === null) {\n // If header is empty, don't consider it for a match\n // Nulling a header provides a way to exclude something from future searching\n continue; // eslint-disable-line no-continue\n }\n const score = Math.min(...column_synonyms.map((s) => levenshtein(header, s)));\n if (score < best_score) {\n best_score = score;\n best_match = i;\n }\n }\n return best_match;\n}\n\n\n/**\n * Return parser configuration for pvalues\n *\n * Returns 1-based column indices, for compatibility with parsers\n * @private\n * @param header_row\n * @param data_rows\n * @returns {{}}\n */\nfunction getPvalColumn(header_row, data_rows) {\n // TODO: Allow overrides\n const LOGPVALUE_FIELDS = ['neg_log_pvalue', 'log_pvalue', 'log_pval', 'logpvalue'];\n const PVALUE_FIELDS = ['pvalue', 'p.value', 'p-value', 'pval', 'p_score', 'p', 'p_value'];\n\n let ps;\n const validateP = (col, data, is_log) => { // Validate pvalues\n const cleaned_vals = _missingToNull(data.map((row) => row[col]));\n try {\n ps = cleaned_vals.map((p) => parsePvalToLog(p, is_log));\n } catch (e) {\n return false;\n }\n return ps.every((val) => !Number.isNaN(val));\n };\n\n const log_p_col = findColumn(LOGPVALUE_FIELDS, header_row);\n const p_col = findColumn(PVALUE_FIELDS, header_row);\n\n if (log_p_col !== null && validateP(log_p_col, data_rows, true)) {\n return {\n pvalue_col: log_p_col + 1,\n is_neg_log_pvalue: true,\n };\n }\n if (p_col && validateP(p_col, data_rows, false)) {\n return {\n pvalue_col: p_col + 1,\n is_neg_log_pvalue: false,\n };\n }\n // Could not auto-determine an appropriate pvalue column\n return null;\n}\n\n/**\n * @private\n */\nfunction getChromPosRefAltColumns(header_row, data_rows) {\n // Returns 1-based column indices, for compatibility with parsers\n // Get from either a marker, or 4 separate columns\n const MARKER_FIELDS = ['snpid', 'marker', 'markerid', 'snpmarker', 'chr:position'];\n const CHR_FIELDS = ['chrom', 'chr', 'chromosome'];\n const POS_FIELDS = ['position', 'pos', 'begin', 'beg', 'bp', 'end', 'ps', 'base_pair_location'];\n\n // TODO: How to handle orienting ref vs effect?\n // Order matters: consider ambiguous field names for ref before alt\n const REF_FIELDS = ['A1', 'ref', 'reference', 'allele0', 'allele1'];\n const ALT_FIELDS = ['A2', 'alt', 'alternate', 'allele1', 'allele2'];\n\n const first_row = data_rows[0];\n let marker_col = findColumn(MARKER_FIELDS, header_row);\n if (marker_col !== null && parseMarker(first_row[marker_col], true)) {\n marker_col += 1;\n return { marker_col };\n }\n\n // If single columns were incomplete, attempt to auto detect 4 separate columns. All 4 must\n // be found for this function to report a match.\n const headers_marked = header_row.slice();\n const find = [\n ['chrom_col', CHR_FIELDS, true],\n ['pos_col', POS_FIELDS, true],\n ['ref_col', REF_FIELDS, false],\n ['alt_col', ALT_FIELDS, false],\n ];\n const config = {};\n for (let i = 0; i < find.length; i++) {\n const [col_name, choices, is_required] = find[i];\n const col = findColumn(choices, headers_marked);\n if (col === null && is_required) {\n return null;\n }\n if (col !== null) {\n config[col_name] = col + 1;\n // Once a column has been assigned, remove it from consideration\n headers_marked[col] = null;\n }\n }\n return config;\n}\n\n/**\n * Identify which columns contain effect size (beta) and stderr of the effect size\n * @private\n * @param {String[]} header_names\n * @param {Array[]} data_rows\n * @returns {{}}\n */\nfunction getEffectSizeColumns(header_names, data_rows) {\n const BETA_FIELDS = ['beta', 'effect_size', 'alt_effsize', 'effect'];\n const STDERR_BETA_FIELDS = ['stderr_beta', 'stderr', 'sebeta', 'effect_size_sd', 'se', 'standard_error'];\n\n function validate_numeric(col, data) {\n const cleaned_vals = _missingToNull(data.map((row) => row[col]));\n let nums;\n try {\n nums = cleaned_vals.filter((val) => val !== null).map((val) => +val);\n } catch (e) {\n return false;\n }\n return nums.every((val) => !Number.isNaN(val));\n }\n\n const beta_col = findColumn(BETA_FIELDS, header_names, 0);\n const stderr_beta_col = findColumn(STDERR_BETA_FIELDS, header_names, 0);\n\n const ret = {};\n if (beta_col !== null && validate_numeric(beta_col, data_rows)) {\n ret.beta_col = beta_col + 1;\n }\n if (stderr_beta_col !== null && validate_numeric(stderr_beta_col, data_rows)) {\n ret.stderr_beta_col = stderr_beta_col + 1;\n }\n return ret;\n}\n\n/**\n * Attempt to guess the correct parser settings given a set of header rows and a set of data rows\n * @private\n * @param {String[]} header_row\n * @param {String[][]} data_rows\n * @param {int} offset Used to convert between 0 and 1-based indexing.\n * @return {Object|null} Returns null if a complete configuration could not suggested.\n */\nfunction guessGWAS(header_row, data_rows, offset = 1) {\n // 1. Find a specific set of info: marker OR chr/pos/ref/alt ; pvalue OR log_pvalue\n // 2. Validate that we will be able to parse the required info: fields present and make sense\n // 3. Based on the field names selected, attempt to infer meaning: verify whether log is used,\n // and check ref/alt vs effect/noneffect\n // 4. Return a parser config object if all tests pass, OR null.\n\n // Normalize case and remove leading comment marks from line for easier comparison\n const headers = header_row.map((item) => (item ? item.toLowerCase() : item));\n headers[0].replace('/^#+/', '');\n // Lists of fields are drawn from Encore (AssocResultReader) and Pheweb (conf_utils.py)\n const pval_config = getPvalColumn(headers, data_rows, offset);\n if (!pval_config) {\n return null;\n }\n headers[pval_config.pvalue_col - 1] = null; // Remove this column from consideration\n const position_config = getChromPosRefAltColumns(headers, data_rows);\n if (!position_config) {\n return null;\n }\n // Remove the position config from consideration for future matches\n Object.keys(position_config).forEach((key) => {\n headers[position_config[key]] = null;\n });\n\n const beta_config = getEffectSizeColumns(headers, data_rows);\n\n if (pval_config && position_config) {\n return Object.assign({}, pval_config, position_config, beta_config || {});\n }\n return null;\n}\n\nexport {\n // Public members\n guessGWAS,\n // Symbols exported for testing\n isHeader as _isHeader,\n getPvalColumn as _getPvalColumn,\n findColumn as _findColumn,\n levenshtein as _levenshtein,\n};\n","/**\n * Optional LocusZoom extension: must be included separately, and after LocusZoom has been loaded\n *\n * This plugin exports helper function, as well as a few optional extra helpers for rendering the plot. The GWAS parsers can be used without registering the plugin.\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, import the helper functions and use them with your layout:\n *\n * ```\n * import { install, makeGWASParser, makeBed12Parser, makePlinkLDParser } from 'locuszoom/esm/ext/lz-parsers';\n * LocusZoom.use(install);\n * ```\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~UserTabixLD} (if the {@link module:ext/lz-tabix-source} extension is loaded first)\n *\n * @module ext/lz-parsers\n */\n\nimport { makeBed12Parser } from './bed';\nimport { makeGWASParser } from './gwas/parsers';\nimport { guessGWAS } from './gwas/sniffers';\nimport { makePlinkLdParser } from './ld';\n\n\n// Most of this plugin consists of standalone functions. But we can add a few simple custom classes to the registry that help to use parsed output\nfunction install(LocusZoom) {\n if (LocusZoom.Adapters.has('TabixUrlSource')) {\n // Custom Tabix adapter depends on another extension being loaded first\n const TabixUrlSource = LocusZoom.Adapters.get('TabixUrlSource');\n const LDServer = LocusZoom.Adapters.get('LDServer');\n\n /**\n * Load user-provided LD from a tabix file, and filter the returned set of records based on a reference variant. (will attempt to choose a reference variant based on the most significant association variant, if no state.ldrefvar is specified)\n * @public\n * @alias module:LocusZoom_Adapters~UserTabixLD\n * @extends module:LocusZoom_Adapters~TabixUrlSource\n * @see {@link module:ext/lz-tabix-source} for required extension and installation instructions\n * @see {@link module:ext/lz-parsers} for required extension and installation instructions\n */\n class UserTabixLD extends TabixUrlSource {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n base._skip_request = true;\n return base;\n }\n\n // NOTE: Reuses a method from another adapter to mix in functionality\n base.ld_refvar = LDServer.prototype.__find_ld_refvar(state, assoc_data);\n return base;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n return Promise.resolve([]);\n }\n return super._performRequest(options);\n }\n\n _annotateRecords(records, options) {\n // A single PLINK LD file could contain several reference variants (SNP_A) in the same region.\n // Only show LD relative to the user-selected refvar in this plot.\n return records.filter((item) => item['variant1'] === options.ld_refvar);\n }\n }\n\n\n LocusZoom.Adapters.add('UserTabixLD', UserTabixLD);\n }\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n// Support UMD (single symbol export)\nconst all = { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser };\n\nexport default all;\n\nexport { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser };\n","import {parseMarker} from '../../../helpers/parse';\n\nimport {\n MISSING_VALUES,\n has,\n parseAlleleFrequency,\n parsePvalToLog, normalizeChr,\n} from '../utils';\n\n\n/**\n * Specify how to parse a GWAS file, given certain column information.\n * Outputs an object with fields in portal API format.\n *\n * All column options must be provided as 1-indexed column IDs (human-friendly argument values)\n * @function\n * @alias module:ext/lz-parsers~makeGWASParser\n * @param options\n * @param [options.marker_col] A single identifier that specifies all of chrom, pos, ref, and alt as a single string field. Eg 1:23_A/C\n * @param [options.chrom_col] Chromosome\n * @param [options.pos_col] Position\n * @param [options.ref_col] Reference allele (relative to human reference genome, eg GRCh37 or 38).\n * @param [options.alt_col] Alt allele. Some programs specify generic A1/A2 instead; it is the job of the user to identify which columns of this GWAS are ref and alt.\n * @param [options.rsid_col] rsID\n * @param options.pvalue_col p-value (or -log10p)\n * @param [options.beta_col]\n * @param [options.stderr_beta_col]\n * @param [options.allele_freq_col] Specify allele frequencies directly\n * @param [options.allele_count_col] Specify allele frequencies in terms of count and n_samples\n * @param [options.n_samples_col]\n * @param [options.is_alt_effect=true] Some programs specify beta and frequency information in terms of ref, others alt. Identify effect allele to orient values to the correct allele.\n * @param [options.is_neg_log_pvalue=false]\n * @param [options.delimiter='\\t'] Since this parser is usually used with tabix data, this is rarely changed (tabix does not accept other delimiters)\n * @return {function(string)} A parser function that can be called on each line of text with the provided options\n */\nfunction makeGWASParser(\n {\n // Required fields\n marker_col, // Identify the variant: marker OR chrom/pos/ref/alt\n chrom_col,\n pos_col,\n ref_col,\n alt_col,\n pvalue_col, // pvalue (or log_pvalue; see options below)\n // Optional fields\n is_neg_log_pvalue = false,\n rsid_col,\n beta_col,\n stderr_beta_col,\n allele_freq_col, // Frequency: given directly, OR in terms of counts\n allele_count_col,\n n_samples_col,\n is_alt_effect = true, // whether effect allele is oriented towards alt. We don't support files like METAL, where ref/alt may switch places per line of the file\n delimiter = '\\t',\n }\n) {\n // Column IDs should be 1-indexed (human friendly)\n if (has(marker_col) && has(chrom_col) && has(pos_col)) {\n throw new Error('Must specify either marker OR chr + pos');\n }\n if (!(has(marker_col) || (has(chrom_col) && has(pos_col)))) {\n throw new Error('Must specify how to locate marker');\n }\n\n if (has(allele_count_col) && has(allele_freq_col)) {\n throw new Error('Allele count and frequency options are mutually exclusive');\n }\n if (has(allele_count_col) && !has(n_samples_col)) {\n throw new Error('To calculate allele frequency from counts, you must also provide n_samples');\n }\n\n\n return (line) => {\n const fields = line.split(delimiter);\n let chr;\n let pos;\n let ref;\n let alt;\n let rsid = null;\n\n let freq;\n let beta = null;\n let stderr_beta = null;\n let alt_allele_freq = null;\n let allele_count;\n let n_samples;\n\n if (has(marker_col)) {\n [chr, pos, ref, alt] = parseMarker(fields[marker_col - 1], false);\n } else if (has(chrom_col) && has(pos_col)) {\n chr = fields[chrom_col - 1];\n pos = fields[pos_col - 1];\n } else {\n throw new Error('Must specify all fields required to identify the variant');\n }\n\n chr = normalizeChr(chr);\n if (chr.startsWith('RS')) {\n throw new Error(`Invalid chromosome specified: value \"${chr}\" is an rsID`);\n }\n\n if (has(ref_col)) {\n ref = fields[ref_col - 1];\n }\n\n if (has(alt_col)) {\n alt = fields[alt_col - 1];\n }\n\n if (has(rsid_col)) {\n rsid = fields[rsid_col - 1];\n }\n\n if (MISSING_VALUES.has(ref)) {\n ref = null;\n }\n if (MISSING_VALUES.has(alt)) {\n alt = null;\n }\n\n if (MISSING_VALUES.has(rsid)) {\n rsid = null;\n } else if (rsid) {\n rsid = rsid.toLowerCase();\n if (!rsid.startsWith('rs')) {\n rsid = `rs${rsid}`;\n }\n }\n\n const log_pval = parsePvalToLog(fields[pvalue_col - 1], is_neg_log_pvalue);\n ref = ref || null;\n alt = alt || null;\n\n if (has(allele_freq_col)) {\n freq = fields[allele_freq_col - 1];\n }\n if (has(allele_count_col)) {\n allele_count = fields[allele_count_col - 1];\n n_samples = fields[n_samples_col - 1];\n }\n\n if (has(beta_col)) {\n beta = fields[beta_col - 1];\n beta = MISSING_VALUES.has(beta) ? null : (+beta);\n }\n\n if (has(stderr_beta_col)) {\n stderr_beta = fields[stderr_beta_col - 1];\n stderr_beta = MISSING_VALUES.has(stderr_beta) ? null : (+stderr_beta);\n }\n\n if (allele_freq_col || allele_count_col) {\n alt_allele_freq = parseAlleleFrequency({\n freq,\n allele_count,\n n_samples,\n is_alt_effect,\n });\n }\n const ref_alt = (ref && alt) ? `_${ref}/${alt}` : '';\n return {\n chromosome: chr,\n position: +pos,\n ref_allele: ref ? ref.toUpperCase() : null,\n alt_allele: alt ? alt.toUpperCase() : null,\n variant: `${chr}:${pos}${ref_alt}`,\n rsid,\n log_pvalue: log_pval,\n beta,\n stderr_beta,\n alt_allele_freq,\n };\n };\n}\n\n\nexport { makeGWASParser };\n","/**\n * Parsers for custom user-specified LD\n */\n\nimport {normalizeChr} from './utils';\nimport {normalizeMarker} from '../../helpers/parse';\n\n/**\n * Parse the output of plink v1.9 R2 calculations relative to one (or a few) target SNPs.\n * See: https://www.cog-genomics.org/plink/1.9/ld and\n * reformatting commands at https://www.cog-genomics.org/plink/1.9/other\n * @function\n * @alias module:ext/lz-parsers~makePlinkLdParser\n * @param {object} options\n * @param {boolean} [options.normalize=true] Normalize fields to expected datatypes and format; if false, returns raw strings as given in the file\n * @return {function} A configured parser function that runs on one line of text from an input file\n */\nfunction makePlinkLdParser({normalize = true} = {}) {\n return (line) => {\n // Sample headers are below: SNP_A and SNP_B are based on ID column of the VCF\n // CHR_A BP_A SNP_A CHR_B BP_B SNP_B R2\n let [chromosome1, position1, variant1, chromosome2, position2, variant2, correlation] = line.trim().split('\\t');\n if (normalize) {\n chromosome1 = normalizeChr(chromosome1);\n chromosome2 = normalizeChr(chromosome2);\n variant1 = normalizeMarker(variant1);\n variant2 = normalizeMarker(variant2);\n position1 = +position1;\n position2 = +position2;\n correlation = +correlation;\n }\n\n return {chromosome1, position1, variant1, chromosome2, position2, variant2, correlation};\n };\n}\n\nexport { makePlinkLdParser };\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-tabix-source.min.js b/dist/ext/lz-tabix-source.min.js index 87d428b1..a6478c12 100644 --- a/dist/ext/lz-tabix-source.min.js +++ b/dist/ext/lz-tabix-source.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.4 */ -var LzTabix;(()=>{"use strict";var e={n:r=>{var t=r&&r.__esModule?()=>r.default:()=>r;return e.d(t,{a:t}),t},d:(r,t)=>{for(var a in t)e.o(t,a)&&!e.o(r,a)&&Object.defineProperty(r,a,{enumerable:!0,get:t[a]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r)},r={};e.d(r,{default:()=>s});const t=tabix;var a=e.n(t);function i(e){const r=e.Adapters.get("BaseAdapter");e.Adapters.add("TabixUrlSource",class extends r{parseInit(e){if(!e.parser_func||!e.url_data)throw new Error("Tabix source is missing required configuration options");this.parser=e.parser_func,this.url_data=e.url_data,this.url_tbi=e.url_tbi||`${this.url_data}.tbi`;const r=e.params||{};if(this.params=r,this._overfetch=r.overfetch||0,this._overfetch<0||this._overfetch>1)throw new Error("Overfetch must be specified as a fraction (0-1) of the requested region size");this._reader_promise=a().urlReader(this.url_data,this.url_tbi).catch((function(){throw new Error("Failed to create a tabix reader from the provided URL")}))}fetchRequest(e){return new Promise(((r,t)=>{const a=e.start,i=e.end,s=this._overfetch*(i-a),o=e.start-s,n=e.end+s;this._reader_promise.then((a=>{a.fetch(e.chr,o,n,(function(e,a){a&&t(new Error("Could not read requested region. This may indicate an error with the .tbi index.")),r(e)}))}))}))}normalizeResponse(e){return e.map(this.parser)}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(i);const s=i;LzTabix=r.default})(); +/*! Locuszoom 0.14.0-beta.1 */ +var LzTabix;(()=>{var t={398:t=>{var i=15,e=-2,n=-3,r=-5,s=13,a=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],o=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],h=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],l=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],f=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],u=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],d=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];function _(){}function c(){this.was=[0]}_.prototype.inflateInit=function(t,i){return t||(t=15),i&&(i=!1),this.istate=new c,this.istate.inflateInit(this,i?-t:t)},_.prototype.inflate=function(t){return null==this.istate?e:this.istate.inflate(this,t)},_.prototype.inflateEnd=function(){if(null==this.istate)return e;var t=istate.inflateEnd(this);return this.istate=null,t},_.prototype.inflateSync=function(){return istate.inflateSync(this)},_.prototype.inflateSetDictionary=function(t,i){return istate.inflateSetDictionary(this,t,i)},c.prototype.inflateReset=function(t){return null==t||null==t.istate?e:(t.total_in=t.total_out=0,t.msg=null,t.istate.mode=0!=t.istate.nowrap?7:0,t.istate.blocks.reset(t,null),0)},c.prototype.inflateEnd=function(t){return null!=this.blocks&&this.blocks.free(t),this.blocks=null,0},c.prototype.inflateInit=function(t,i){return t.msg=null,this.blocks=null,nowrap=0,i<0&&(i=-i,nowrap=1),i<8||i>15?(this.inflateEnd(t),e):(this.wbits=i,t.istate.blocks=new v(t,0!=t.istate.nowrap?null:this,1<>4)>t.istate.wbits){t.istate.mode=s,t.msg="invalid window size",t.istate.marker=5;break}t.istate.mode=1;case 1:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,o=255&t.next_in[t.next_in_index++],((t.istate.method<<8)+o)%31!=0){t.istate.mode=s,t.msg="incorrect header check",t.istate.marker=5;break}if(0==(32&o)){t.istate.mode=7;break}t.istate.mode=2;case 2:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=3;case 3:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=4;case 4:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=5;case 5:return 0==t.avail_in?a:(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.adler=t.istate.need,t.istate.mode=6,2);case 6:return t.istate.mode=s,t.msg="need dictionary",t.istate.marker=0,e;case 7:if((a=t.istate.blocks.proc(t,a))==n){t.istate.mode=s,t.istate.marker=0;break}if(0==a&&(a=i),1!=a)return a;if(a=i,t.istate.blocks.reset(t,t.istate.was),0!=t.istate.nowrap){t.istate.mode=12;break}t.istate.mode=8;case 8:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=9;case 9:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=10;case 10:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=11;case 11:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.istate.was[0]!=t.istate.need){t.istate.mode=s,t.msg="incorrect data check",t.istate.marker=5;break}t.istate.mode=12;case 12:return 1;case s:return n;default:return e}},c.prototype.inflateSetDictionary=function(t,i,r){var s=0,a=r;return null==t||null==t.istate||6!=t.istate.mode?e:t._adler.adler32(1,i,0,r)!=t.adler?n:(t.adler=t._adler.adler32(0,null,0,0),a>=1<>>1){case 0:o>>>=3,o>>>=r=7&(h-=3),h-=r,this.mode=1;break;case 1:w(v=new Int32Array(1),p=new Int32Array(1),m=[],y=[],t),this.codes.init(v[0],p[0],m[0],0,y[0],0,t),o>>>=3,h-=3,this.mode=6;break;case 2:o>>>=3,h-=3,this.mode=3;break;case 3:return o>>>=3,h-=3,this.mode=s,t.msg="invalid block type",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i)}break;case 1:for(;h<32;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>16&65535)!=(65535&o))return this.mode=s,t.msg="invalid stored block lengths",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.left=65535&o,o=h=0,this.mode=0!=this.left?2:0!=this.last?7:0;break;case 2:if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);if(0==d&&(u==end&&0!=read&&(d=(u=0)f&&(r=f),r>d&&(r=d),g(t.next_in,l,this.window,u,r),l+=r,f-=r,u+=r,d-=r,0!=(this.left-=r))break;this.mode=0!=this.last?7:0;break;case 3:for(;h<14;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<29||(r>>5&31)>29)return this.mode=9,t.msg="too many length or distance symbols",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);if(r=258+(31&r)+(r>>5&31),null==this.blens||this.blens.length>>=14,h-=14,this.index=0,mode=4;case 4:for(;this.index<4+(this.table>>>10);){for(;h<3;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>=3,h-=3}for(;this.index<19;)this.blens[b[this.index++]]=0;if(this.bb[0]=7,0!=(r=this.inftree.inflate_trees_bits(this.blens,this.bb,this.tb,this.hufts,t)))return(i=r)==n&&(this.blens=null,this.mode=9),this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);this.index=0,this.mode=5;case 5:for(;r=this.table,this.index<258+(31&r)+(r>>5&31);){var c,x;for(r=this.bb[0];h>>=r,h-=r,this.blens[this.index++]=x;else{for(_=18==x?7:x-14,c=18==x?11:3;h>>=r)&a[_],o>>>=_,h-=_,(_=this.index)+c>258+(31&(r=this.table))+(r>>5&31)||16==x&&_<1)return this.blens=null,this.mode=9,t.msg="invalid bit length repeat",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);x=16==x?this.blens[_-1]:0;do{this.blens[_++]=x}while(0!=--c);this.index=_}}this.tb[0]=-1;var v=new Int32Array(1),p=new Int32Array(1),m=new Int32Array(1),y=new Int32Array(1);if(v[0]=9,p[0]=6,r=this.table,0!=(r=this.inftree.inflate_trees_dynamic(257+(31&r),1+(r>>5&31),this.blens,v,p,m,y,this.hufts,t)))return r==n&&(this.blens=null,this.mode=s),i=r,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.codes.init(v[0],p[0],this.hufts,m[0],this.hufts,y[0],t),this.mode=6;case 6:if(this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,1!=(i=this.codes.proc(this,t,i)))return this.inflate_flush(t,i);if(i=0,this.codes.free(t),l=t.next_in_index,f=t.avail_in,o=this.bitb,h=this.bitk,d=(u=this.write)t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,(s+=e)==this.end&&(s=0,this.write==this.end&&(this.write=0),(e=this.write-s)>t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,s+=e),t.next_out_index=n,this.read=s,i};function p(){}function m(){}function w(t,i,e,n,r){return t[0]=9,i[0]=5,e[0]=o,n[0]=h,0}p.prototype.init=function(t,i,e,n,r,s,a){this.mode=0,this.lbits=t,this.dbits=i,this.ltree=e,this.ltree_index=n,this.dtree=r,this.dtree_index=s,this.tree=null},p.prototype.proc=function(t,i,r){var s,o,h,l,f,u,d,_=0,c=0,x=0;for(x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)=258&&l>=10&&(t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,r=this.inflate_fast(this.lbits,this.dbits,this.ltree,this.ltree_index,this.dtree,this.dtree_index,t,i),x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)>>=this.tree[o+1],c-=this.tree[o+1],0==(h=this.tree[o])){this.lit=this.tree[o+2],this.mode=6;break}if(0!=(16&h)){this.get=15&h,this.len=this.tree[o+2],this.mode=2;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}if(0!=(32&h)){this.mode=7;break}return this.mode=9,i.msg="invalid literal/length code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 2:for(s=this.get;c>=s,c-=s,this.need=this.dbits,this.tree=this.dtree,this.tree_index=this.dtree_index,this.mode=3;case 3:for(s=this.need;c>=this.tree[o+1],c-=this.tree[o+1],0!=(16&(h=this.tree[o]))){this.get=15&h,this.dist=this.tree[o+2],this.mode=4;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}return this.mode=9,i.msg="invalid distance code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 4:for(s=this.get;c>=s,c-=s,this.mode=5;case 5:for(d=f-this.dist;d<0;)d+=t.end;for(;0!=this.len;){if(0==u&&(f==t.end&&0!=t.read&&(u=(f=0)7&&(c-=8,l++,x--),t.write=f,r=t.inflate_flush(i,r),u=(f=t.write)>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15,k=u[S+2]+(c&a[_]),c>>=_,x-=_;x<15;)v--,c|=(255&l.next_in[b++])<>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15;x<_;)v--,c|=(255&l.next_in[b++])<>=_,x-=_,m-=k,p>=A)C=p-A,h.window[p++]=h.window[C++],h.window[p++]=h.window[C++],k-=2;else{C=p-A;do{C+=h.end}while(C<0);if(k>(_=h.end-C)){if(k-=_,p-C>0&&_>p-C)do{h.window[p++]=h.window[C++]}while(0!=--_);else g(h.window,C,h.window,p,_),p+=_,C+=_,_=0;C=0}}do{h.window[p++]=h.window[C++]}while(0!=--k);break}if(0!=(64&_))return l.msg="invalid distance code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n;f+=u[S+2],_=u[S=3*(d+(f+=c&a[_]))]}break}if(0!=(64&_))return 0!=(32&_)?(v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,1):(l.msg="invalid literal/length code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n);if(f+=u[S+2],0==(_=u[S=3*(d+(f+=c&a[_]))])){c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--;break}}else c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--}while(m>=258&&v>=10);return v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,0},m.prototype.huft_build=function(t,e,s,a,o,h,l,f,u,d,_){var c,x,b,v,p,m,w,y,k,A,C,S,R,I,L;A=0,p=s;do{this.c[t[e+A]]++,A++,p--}while(0!=p);if(this.c[0]==s)return l[0]=-1,f[0]=0,0;for(y=f[0],m=1;m<=i&&0==this.c[m];m++);for(w=m,yp&&(y=p),f[0]=y,I=1<S+y;){if(v++,L=(L=b-(S+=y))>y?y:L,(x=1<<(m=w-S))>c+1&&(x-=c+1,R=w,m1440)return n;this.u[v]=C=this.hn[0],this.hn[0]+=L,0!=v?(this.x[v]=p,this.r[0]=m,this.r[1]=y,m=p>>>S-y,this.r[2]=C-this.u[v-1]-m,g(this.r,0,u,3*(this.u[v-1]+m),3)):l[0]=C}for(this.r[1]=w-S,A>=s?this.r[0]=192:_[A]>>S;m>>=1)p^=m;for(p^=m,k=(1<257?(x==n?c.msg="oversubscribed distance tree":x==r?(c.msg="incomplete distance tree",x=n):-4!=x&&(c.msg="empty distance tree with lengths",x=n),x):0)},m.prototype.initWorkArea=function(t){null==this.hn&&(this.hn=new Int32Array(1),this.v=new Int32Array(t),this.c=new Int32Array(16),this.r=new Int32Array(3),this.u=new Int32Array(i),this.x=new Int32Array(16)),this.v.length100?k(new Uint8Array(t.buffer,t.byteOffset+i,r),e,n):function(t,i,e,n,r){for(var s=0;s{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},e.o=(t,i)=>Object.prototype.hasOwnProperty.call(t,i);var n={};(()=>{"use strict";e.d(n,{default:()=>q});function t(t){return r(i(s(t)))}function i(t){return o(h(a(t),8*t.length))}function r(t){for(var i="",e=t.length,n=0;n8*t.length?i+="":i+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(r>>>6*(3-s)&63);return i}function s(t){for(var i,e,n="",r=-1;++r>>6&31,128|63&i):i<=65535?n+=String.fromCharCode(224|i>>>12&15,128|i>>>6&63,128|63&i):i<=2097151&&(n+=String.fromCharCode(240|i>>>18&7,128|i>>>12&63,128|i>>>6&63,128|63&i));return n}function a(t){for(var i=Array(t.length>>2),e=0;e>5]|=(255&t.charCodeAt(e/8))<<24-e%32;return i}function o(t){for(var i="",e=0;e<32*t.length;e+=8)i+=String.fromCharCode(t[e>>5]>>>24-e%32&255);return i}function h(t,i){t[i>>5]|=128<<24-i%32,t[15+(i+64>>9<<4)]=i;for(var e=Array(80),n=1732584193,r=-271733879,s=-1732584194,a=271733878,o=-1009589776,h=0;h>16)+(i>>16)+(e>>16)<<16|65535&e}function d(t,i){return t<>>32-i}function _(t){this.value=t,this.listeners=[]}function c(){this.queue=[]}_.prototype.addListener=function(t){this.listeners.push(t)},_.prototype.addListenerAndFire=function(t){this.listeners.push(t),t(this.value)},_.prototype.removeListener=function(t){var i,e;i=this.listeners,(e=function(t,i){if(!t)return-1;for(var e=0;e=0&&i.splice(e,1)},_.prototype.get=function(){return this.value},_.prototype.set=function(t){this.value=t;for(var i=0;i=0&&navigator.userAgent.indexOf("Chrome")<0;function m(t){if(!t)return null;for(var i=new Uint8Array(t.length),e=0;e1e8)throw"Monster fetch!";r.setRequestHeader("Range","bytes="+e.start+"-"+e.end),e.end-e.start+1}r.onreadystatechange=function(){if(4==r.readyState)return 200==r.status||206==r.status?i(r.responseText):i(null)},e.opts.credentials&&(r.withCredentials=!0),r.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))},b.prototype.salted=function(){var t=function(t){var i={};for(var e in t)i[e]=t[e];return i}(this.opts);return t.salt=!0,new b(this.url,this.start,this.end,t)},b.prototype.getURL=function(){return this.opts.resolver?this.opts.resolver(this.url).then((function(t){return"string"==typeof t?t:t.url})):Promise.resolve(this.url)},b.prototype.fetch=function(i,e){var n=this,r=(e=e||{}).attempt||1,s=e.truncatedLength;if(r>3)return i(null);this.getURL().then((function(a){try{var o;e.timeout&&!n.opts.credentials&&(o=setTimeout((function(){return console.log("timing out "+a),l.abort(),i(null,"Timeout")}),e.timeout));var h,l=new XMLHttpRequest;if((p||n.opts.salt)&&a.indexOf("?")<0&&(a=a+"?salt="+t(Date.now()+","+ ++v)),l.open("GET",a,!0),l.overrideMimeType("text/plain; charset=x-user-defined"),n.end){if(n.end-n.start>1e8)throw"Monster fetch!";l.setRequestHeader("Range","bytes="+n.start+"-"+n.end),h=n.end-n.start+1}l.responseType="arraybuffer",l.onreadystatechange=function(){if(4==l.readyState){if(o&&clearTimeout(o),200==l.status||206==l.status){if(l.response){var t=l.response.byteLength;return!h||h==t||s&&t==s?i(l.response):n.fetch(i,{attempt:r+1,truncatedLength:t})}if(l.mozResponseArrayBuffer)return i(l.mozResponseArrayBuffer);var e=l.responseText;return!h||h==e.length||s&&e.length==s?i(m(l.responseText)):n.fetch(i,{attempt:r+1,truncatedLength:e.length})}return n.fetch(i,{attempt:r+1})}},n.opts.credentials&&(l.withCredentials=!0),l.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))};var w=new ArrayBuffer(8);new Uint8Array(w),new Float32Array(w);function y(t,i){return t[i+3]<<24|t[i+2]<<16|t[i+1]<<8|t[i]}var g=e(398);function k(t,i){this.block=t,this.offset=i}function A(t,i,e){var n=4294967296*(255&t[i+6])+16777216*(255&t[i+5])+65536*(255&t[i+4])+256*(255&t[i+3])+(255&t[i+2]),r=t[i+1]<<8|t[i];return 0!=n||0!=r||e?new k(n,r):null}function C(t,i){i=Math.min(i||1,t.byteLength-50);for(var e=[],n=[0],r=0;n[0]n._max&&(n._max=t._max):(e.push(n),n=t)})),e.push(n),this._ranges=e}function L(t,i){return t._mini._min?1:t._maxt._max?1:0}R.prototype.min=function(){return this._min},R.prototype.max=function(){return this._max},R.prototype.contains=function(t){return t>=this._min&&t<=this._max},R.prototype.isContiguous=function(){return!0},R.prototype.ranges=function(){return[this]},R.prototype._pushRanges=function(t){t.push(this)},R.prototype.toString=function(){return"["+this._min+"-"+this._max+"]"},I.prototype.min=function(){return this._ranges[0].min()},I.prototype.max=function(){return this._ranges[this._ranges.length-1].max()},I.prototype.lower_bound=function(t){var i=this.ranges();if(t>this.max())return i.length;if(ti[r]._max)e=r+1;else{if(!(tt._max&&(t._max=e[n]._max),this._ranges.splice(i,n-i+1,t)}}else this._ranges.push(t)},I.prototype.isContiguous=function(){return this._ranges.length>1},I.prototype.ranges=function(){return this._ranges},I.prototype._pushRanges=function(t){for(var i=0;i0&&(t+=","),t+=this._ranges[i].toString();return t};function T(){}function U(t,i){return new Promise((function(e,n){var r,s,a,o;r=new b(t),s=new b(i),a=function(t,i){i?n(i):e(t)},(o=new T).data=r,o.tbi=s,o.tbi.fetch((function(t){if(!t)return a(null,"Couldn't access Tabix");var i=C(t,t.byteLength),e=new Uint8Array(i);if(21578324!=y(e,0))return a(null,"Not a tabix index");var n=y(e,4);o.format=y(e,8),o.colSeq=y(e,12),o.colStart=y(e,16),o.colEnd=y(e,20),o.meta=y(e,24),o.skip=y(e,28),y(e,32),o.indices=[];var r=36;o.chrToIndex={},o.indexToChr=[];for(var s=0;s0&&(p+=65536),p0&&(o.indices[u]=new Uint8Array(i,d,r-d))}o.headerMax=f,a(o)}),{timeout:5e4})}))}function E(t){const i=t.Adapters.get("BaseLZAdapter");t.Adapters.add("TabixUrlSource",class extends i{constructor(t){if(super(t),!t.parser_func||!t.url_data&&!t.reader)throw new Error("Tabix source is missing required configuration options");if(this.parser=t.parser_func,this.url_data=t.url_data,this.url_tbi=t.url_tbi||`${this.url_data}.tbi`,this._overfetch=t.overfetch||0,this._overfetch<0||this._overfetch>1)throw new Error("Overfetch must be specified as a fraction (0-1) of the requested region size");this.url_data?this._reader_promise=U(this.url_data,this.url_tbi).catch((function(){throw new Error("Failed to create a tabix reader from the provided URL")})):this._reader_promise=Promise.resolve(t.reader)}_performRequest(t){return new Promise(((i,e)=>{const n=t.start,r=t.end,s=this._overfetch*(r-n),a=t.start-s,o=t.end+s;this._reader_promise.then((n=>{n.fetch(t.chr,a,o,(function(t,n){n&&e(new Error("Could not read requested region. This may indicate an error with the .tbi index.")),i(t)}))}))}))}_normalizeResponse(t){return t.map(this.parser)}})}T.prototype.blocksForRange=function(t,i,e){var n=this.indices[t];if(!n)return[];for(var r=function(t,i){var e,n=[];for(--i,n.push(0),e=1+(t>>26);e<=1+(i>>26);++e)n.push(e);for(e=9+(t>>23);e<=9+(i>>23);++e)n.push(e);for(e=73+(t>>20);e<=73+(i>>20);++e)n.push(e);for(e=585+(t>>17);e<=585+(i>>17);++e)n.push(e);for(e=4681+(t>>14);e<=4681+(i>>14);++e)n.push(e);return n}(i,e),s=[],a=0;a>14,v-1),w=Math.min(e>>14,v-1);for(a=m;a<=w;++a){var g=A(n,f+4+8*a);g&&((!p||g.block=p.block&&C.maxv.offset>=p.offset&&k.push(C)}h=k;var R=[];for(a=0;a0){var L=R[0];for(a=1;a=a.length)return n(l);if(h){var s=new Uint8Array(h);return r.readRecords(s,a[f].minv.offset,l,i,e,o),h=null,++f,t()}var u=a[f],d=u.minv.block,_=u.maxv.block+65536;r.data.slice(d,_-d).fetch((function(i){try{return h=C(i,u.maxv.block-u.minv.block+1),t()}catch(t){return n(null,t)}}))}()}catch(t){n(null,t)}},T.prototype.readRecords=function(t,i,e,n,r,s){t:for(;;){for(var a="";i0&&(f=parseInt(h[this.colEnd-1])),65536&this.format&&++l,l<=r&&f>=n&&e.push(a)}continue t}a+=String.fromCharCode(o)}return}},T.prototype.fetchHeader=function(t,i){var e={metaOnly:!0,skipped:!1,nLines:0};i=i||e,Object.keys(e).forEach((function(t){i.hasOwnProperty(t)||(i[t]=e[t])}));var n=this;n.data.slice(0,n.headerMax).fetch((function(e){if(!e)return t(null,"Fetch failed");for(var r=new Uint8Array(C(e,e.byteLength)),s=0,a="",o=[];s {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = tabix;","/**\n * An adapter that fetches data from a remote Tabix-indexed datafile, instead of a RESTful API.\n * Requires a generic user-specified parser function.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~TabixUrlSource}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - Vendor assets\n * - LocusZoom\n * - tabix-reader (available via NPM or a related CDN)\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import LzTabixSource from 'locuszoom/esm/ext/lz-tabix-source';\n * LocusZoom.use(LzTabixSource);\n * ```\n *\n * Then use the Adapter made available by this extension. For example:\n *\n * ```javascript\n * data_sources.add(\"assoc\", [\"TabixUrlSource\", {\n * url_data: 'https://s3-bucket.example/tabix-indexed-bgzip-file.gz',\n * parser_func: (line_of_text) => object_of_parsed_data_for_this_line,\n * // Tabix performs region queries. If you are fetching interval data (one end outside the edge of the plot), then\n * // \"overfetching\" can help to ensure that data partially outside the view region is retrieved\n * // If you are fetching single-point data like association summary stats, then overfetching is unnecessary\n * params: { overfetch: 0.25 }\n * }]);\n * ```\n *\n * @module\n */\nimport tabix from 'tabix-reader';\n\n\nfunction install(LocusZoom) {\n const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter');\n\n /**\n * Loads data from a remote Tabix file (if the file host has been configured with proper\n * CORS and Range header support). For instructions on how to configure a remote file host such as S3 or\n * Google Cloud storage to serve files in the manner required, see:\n * https://docs.cancergenomicscloud.org/docs/enabling-cross-origin-resource-sharing-cors#CORS\n *\n * @alias module:LocusZoom_Adapters~TabixUrlSource\n * @see {@link module:ext/lz-tabix-source} for required extension and installation instructions\n * @see module:LocusZoom_Adapters~BaseApiAdapter\n * @param {function} config.parser_func A function that parses a single line of text and returns (usually) a\n * structured object of data fields\n * @param {string} config.url_data The URL for the bgzipped and tabix-indexed file\n * @param {string} [config.url_tbi] The URL for the tabix index. Defaults to `url_data` + '.tbi'\n * @param {number} [config.params.overfetch = 0] Optionally fetch more data than is required to satisfy the\n * region query. (specified as a fraction of the region size, 0-1).\n * Useful for sources where interesting features might lie near the edges of the plot, eg BED track intervals.\n */\n class TabixUrlSource extends BaseAdapter {\n parseInit(init) {\n if (!init.parser_func || !init.url_data) {\n throw new Error('Tabix source is missing required configuration options');\n }\n this.parser = init.parser_func;\n // TODO: In the future, accept a pre-configured reader instance (as an alternative to the URL). Most useful\n // for UIs that want to validate the tabix file before adding it to the plot, like LocalZoom.\n this.url_data = init.url_data;\n this.url_tbi = init.url_tbi || `${this.url_data}.tbi`;\n\n // In tabix mode, sometimes we want to fetch a slightly larger region than is displayed, in case a\n // feature is on the edge of what the tabix query would return.\n // Specify overfetch in units of % of total region size. (\"fetch 10% extra before and after\")\n const params = init.params || {};\n this.params = params;\n this._overfetch = params.overfetch || 0;\n\n if (this._overfetch < 0 || this._overfetch > 1) {\n throw new Error('Overfetch must be specified as a fraction (0-1) of the requested region size');\n }\n\n // Assuming that the `tabix-reader` library has been loaded via a CDN, this will create the reader\n // Since fetching the index is a remote operation, all reader usages will be via an async interface.\n this._reader_promise = tabix.urlReader(this.url_data, this.url_tbi).catch(function () {\n throw new Error('Failed to create a tabix reader from the provided URL');\n });\n }\n\n fetchRequest(state /*, chain, fields */) {\n return new Promise((resolve, reject) => {\n // Ensure that the reader is fully created (and index available), then make a query\n const region_start = state.start;\n const region_end = state.end;\n const extra_amount = this._overfetch * (region_end - region_start);\n\n const start = state.start - extra_amount;\n const end = state.end + extra_amount;\n this._reader_promise.then((reader) => {\n reader.fetch(state.chr, start, end, function (data, err) {\n if (err) {\n reject(new Error('Could not read requested region. This may indicate an error with the .tbi index.'));\n }\n resolve(data);\n });\n });\n });\n }\n\n normalizeResponse(data) {\n // Parse the data from lines of text to objects\n return data.map(this.parser);\n }\n }\n\n LocusZoom.Adapters.add('TabixUrlSource', TabixUrlSource);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/./node_modules/jszlib/js/inflate.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./node_modules/tabix-reader/src/lib/sha1.js","webpack://[name]/./node_modules/tabix-reader/src/lib/utils.js","webpack://[name]/./node_modules/tabix-reader/src/lib/bin.js","webpack://[name]/./node_modules/tabix-reader/src/lib/lh3utils.js","webpack://[name]/./node_modules/tabix-reader/src/lib/spans.js","webpack://[name]/./node_modules/tabix-reader/src/lib/tabix.js","webpack://[name]/./node_modules/tabix-reader/src/main.js","webpack://[name]/./esm/ext/lz-tabix-source.js"],"names":["BMAX","Z_STREAM_ERROR","Z_DATA_ERROR","Z_BUF_ERROR","BAD","inflate_mask","fixed_tl","fixed_td","cplens","cplext","cpdist","cpdext","ZStream","Inflate","this","was","prototype","inflateInit","w","nowrap","istate","inflate","f","inflateEnd","ret","inflateSync","inflateSetDictionary","dictionary","dictLength","inflateReset","z","total_in","total_out","msg","mode","blocks","reset","free","wbits","InfBlocks","r","b","next_in","avail_in","method","next_in_index","marker","need","adler","proc","index","length","_adler","adler32","set_dictionary","mark","n","p","m","inflateSyncPoint","sync_point","INFBLOCKS_BORDER","checkfn","hufts","Int32Array","MANY","window","Uint8Array","end","left","table","blens","bb","tb","codes","InfCodes","last","bitk","bitb","read","write","check","inftree","InfTree","c","t","k","q","inflate_flush","inflate_trees_fixed","bl","bd","tl","td","init","arrayCopy","i","inflate_trees_bits","j","inflate_trees_dynamic","d","start","next_out_index","avail_out","next_out","tl_index","td_index","lbits","dbits","ltree","ltree_index","dtree","dtree_index","tree","s","tindex","e","inflate_fast","tree_index","lit","get","len","dist","tp","tp_index","ml","md","tp_index_t_3","huft_build","bindex","hp","hn","v","a","g","h","l","mask","xp","y","x","u","result","initWorkArea","nl","nd","vsize","hasSubarray","subarray","src","srcOffset","dest","destOffset","count","arrayCopy_fast","BYTES_PER_ELEMENT","buffer","byteOffset","arrayCopy_slow","set","module","exports","inflateBuffer","afterUncOffset","byteLength","oBlockList","totalSize","obuf","status","newob","push","out","cursor","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","definition","key","o","Object","defineProperty","enumerable","obj","prop","hasOwnProperty","call","rstr2b64","rstr_sha1","str2rstr_utf8","binb2rstr","binb_sha1","rstr2binb","input","output","triplet","charCodeAt","charAt","String","fromCharCode","Array","olda","oldb","oldc","oldd","olde","bit_rol","safe_add","sha1_ft","sha1_kt","lsw","num","cnt","Observed","value","listeners","Awaited","queue","addListener","addListenerAndFire","removeListener","arrayIndexOf","splice","provide","undefined","res","await","blob","URLFetchable","url","opts","trim","replace","slice","webkitSlice","salted","fetch","callback","reader","FileReader","onloadend","ev","bstringToBuffer","readAsBinaryString","FileReaderSync","readAsArrayBuffer","ns","ne","seed","isSafari","navigator","userAgent","indexOf","ba","fetchAsText","thisB","getURL","then","req","XMLHttpRequest","salt","Date","now","open","setRequestHeader","onreadystatechange","readyState","responseText","credentials","withCredentials","send","catch","err","console","log","shallowCopy","resolver","urlOrObj","Promise","resolve","attempt","truncatedLength","timeout","setTimeout","abort","overrideMimeType","responseType","clearTimeout","response","mozResponseArrayBuffer","convertBuffer","ArrayBuffer","Float32Array","readInt","offset","Vob","block","readVob","allowZero","bint","unbgzf","data","lim","Math","min","ptr","xlen","unc","Chunk","minv","maxv","toString","Range","max","_min","_max","_Compound","ranges","sorted","sort","_rangeOrder","merged","current","shift","forEach","range","_ranges","contains","pos","isContiguous","_pushRanges","lower_bound","floor","lb","insertRange","ub","ri","TabixFile","urlReader","dataUrl","indexUrl","reject","tbi","tabix","header","unchead","uncba","nref","format","colSeq","colStart","colEnd","meta","skip","indices","chrToIndex","indexToChr","name","ch","substring","minBlockIndex","ref","blockStart","nbin","nintv","bi","headerMax","install","LocusZoom","BaseLZAdapter","Adapters","add","config","super","parser_func","url_data","Error","parser","url_tbi","_overfetch","overfetch","_reader_promise","options","region_start","region_end","extra_amount","chr","records","map","blocksForRange","refId","intBinsL","beg","list","reg2bins","intBins","leafChunks","otherChunks","bin","nchnk","cs","ce","lowest","minLin","maxLin","prunedOtherChunks","chnk","intChunks","c0","c1","dif","mergedChunks","cur","nc","chrId","chunks","canonicalChr","tramp","readRecords","fetchMin","fetchMax","sink","LINE_LOOP","line","toks","split","fmin","parseInt","fmax","fetchHeader","defaults","metaOnly","skipped","nLines","keys","self","lines","use"],"mappings":"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E,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,EAAE,EAAE,EAAG,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,EAAG,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,GAAG,EAAE,IAC7B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,EAAE,EAAE,EAAG,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,EAAG,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,EAAE,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC7B,GAAG,EAAE,EAAG,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,GAAG,EAAE,IAC7B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,IAC7B,EAAE,EAAE,EAAG,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,EAAG,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,EAAE,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC7B,GAA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,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC7B,GAAG,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,GAAG,EAAE,IAC/B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,IAC7B,EAAE,EAAE,EAAG,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,EAAG,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,EAAE,EAAE,EAAG,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,EAAG,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,GAAG,EAAE,IAC7B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,EAAE,EAAE,EAAG,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,EAAG,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,IAC5B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,EAAE,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC7B,GAAG,EAAE,EAAG,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,GAAG,EAAE,IAC7B,GAAG,EAAE,GAAI,EAAE,EAAE,IAAK,EAAE,EAAE,GAAI,EAAE,EAAE,IAC9B,GAAG,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,GAAI,EAAE,EAAE,IAC7B,EAAE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","file":"ext/lz-tabix-source.min.js","sourcesContent":["/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */\n\n// \n// Javascript ZLib\n// By Thomas Down 2010-2011\n//\n// Based very heavily on portions of jzlib (by ymnk@jcraft.com), who in\n// turn credits Jean-loup Gailly and Mark Adler for the original zlib code.\n//\n// inflate.js: ZLib inflate code\n//\n\n//\n// Shared constants\n//\n\nvar MAX_WBITS=15; // 32K LZ77 window\nvar DEF_WBITS=MAX_WBITS;\nvar MAX_MEM_LEVEL=9;\nvar MANY=1440;\nvar BMAX = 15;\n\n// preset dictionary flag in zlib header\nvar PRESET_DICT=0x20;\n\nvar Z_NO_FLUSH=0;\nvar Z_PARTIAL_FLUSH=1;\nvar Z_SYNC_FLUSH=2;\nvar Z_FULL_FLUSH=3;\nvar Z_FINISH=4;\n\nvar Z_DEFLATED=8;\n\nvar Z_OK=0;\nvar Z_STREAM_END=1;\nvar Z_NEED_DICT=2;\nvar Z_ERRNO=-1;\nvar Z_STREAM_ERROR=-2;\nvar Z_DATA_ERROR=-3;\nvar Z_MEM_ERROR=-4;\nvar Z_BUF_ERROR=-5;\nvar Z_VERSION_ERROR=-6;\n\nvar METHOD=0; // waiting for method byte\nvar FLAG=1; // waiting for flag byte\nvar DICT4=2; // four dictionary check bytes to go\nvar DICT3=3; // three dictionary check bytes to go\nvar DICT2=4; // two dictionary check bytes to go\nvar DICT1=5; // one dictionary check byte to go\nvar DICT0=6; // waiting for inflateSetDictionary\nvar BLOCKS=7; // decompressing blocks\nvar CHECK4=8; // four check bytes to go\nvar CHECK3=9; // three check bytes to go\nvar CHECK2=10; // two check bytes to go\nvar CHECK1=11; // one check byte to go\nvar DONE=12; // finished check, done\nvar BAD=13; // got an error--stay here\n\nvar inflate_mask = [0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff];\n\nvar IB_TYPE=0; // get type bits (3, including end bit)\nvar IB_LENS=1; // get lengths for stored\nvar IB_STORED=2;// processing stored block\nvar IB_TABLE=3; // get table lengths\nvar IB_BTREE=4; // get bit lengths tree for a dynamic block\nvar IB_DTREE=5; // get length, distance trees for a dynamic block\nvar IB_CODES=6; // processing fixed or dynamic block\nvar IB_DRY=7; // output remaining window bytes\nvar IB_DONE=8; // finished last block, done\nvar IB_BAD=9; // ot a data error--stuck here\n\nvar fixed_bl = 9;\nvar fixed_bd = 5;\n\nvar fixed_tl = [\n 96,7,256, 0,8,80, 0,8,16, 84,8,115,\n 82,7,31, 0,8,112, 0,8,48, 0,9,192,\n 80,7,10, 0,8,96, 0,8,32, 0,9,160,\n 0,8,0, 0,8,128, 0,8,64, 0,9,224,\n 80,7,6, 0,8,88, 0,8,24, 0,9,144,\n 83,7,59, 0,8,120, 0,8,56, 0,9,208,\n 81,7,17, 0,8,104, 0,8,40, 0,9,176,\n 0,8,8, 0,8,136, 0,8,72, 0,9,240,\n 80,7,4, 0,8,84, 0,8,20, 85,8,227,\n 83,7,43, 0,8,116, 0,8,52, 0,9,200,\n 81,7,13, 0,8,100, 0,8,36, 0,9,168,\n 0,8,4, 0,8,132, 0,8,68, 0,9,232,\n 80,7,8, 0,8,92, 0,8,28, 0,9,152,\n 84,7,83, 0,8,124, 0,8,60, 0,9,216,\n 82,7,23, 0,8,108, 0,8,44, 0,9,184,\n 0,8,12, 0,8,140, 0,8,76, 0,9,248,\n 80,7,3, 0,8,82, 0,8,18, 85,8,163,\n 83,7,35, 0,8,114, 0,8,50, 0,9,196,\n 81,7,11, 0,8,98, 0,8,34, 0,9,164,\n 0,8,2, 0,8,130, 0,8,66, 0,9,228,\n 80,7,7, 0,8,90, 0,8,26, 0,9,148,\n 84,7,67, 0,8,122, 0,8,58, 0,9,212,\n 82,7,19, 0,8,106, 0,8,42, 0,9,180,\n 0,8,10, 0,8,138, 0,8,74, 0,9,244,\n 80,7,5, 0,8,86, 0,8,22, 192,8,0,\n 83,7,51, 0,8,118, 0,8,54, 0,9,204,\n 81,7,15, 0,8,102, 0,8,38, 0,9,172,\n 0,8,6, 0,8,134, 0,8,70, 0,9,236,\n 80,7,9, 0,8,94, 0,8,30, 0,9,156,\n 84,7,99, 0,8,126, 0,8,62, 0,9,220,\n 82,7,27, 0,8,110, 0,8,46, 0,9,188,\n 0,8,14, 0,8,142, 0,8,78, 0,9,252,\n 96,7,256, 0,8,81, 0,8,17, 85,8,131,\n 82,7,31, 0,8,113, 0,8,49, 0,9,194,\n 80,7,10, 0,8,97, 0,8,33, 0,9,162,\n 0,8,1, 0,8,129, 0,8,65, 0,9,226,\n 80,7,6, 0,8,89, 0,8,25, 0,9,146,\n 83,7,59, 0,8,121, 0,8,57, 0,9,210,\n 81,7,17, 0,8,105, 0,8,41, 0,9,178,\n 0,8,9, 0,8,137, 0,8,73, 0,9,242,\n 80,7,4, 0,8,85, 0,8,21, 80,8,258,\n 83,7,43, 0,8,117, 0,8,53, 0,9,202,\n 81,7,13, 0,8,101, 0,8,37, 0,9,170,\n 0,8,5, 0,8,133, 0,8,69, 0,9,234,\n 80,7,8, 0,8,93, 0,8,29, 0,9,154,\n 84,7,83, 0,8,125, 0,8,61, 0,9,218,\n 82,7,23, 0,8,109, 0,8,45, 0,9,186,\n 0,8,13, 0,8,141, 0,8,77, 0,9,250,\n 80,7,3, 0,8,83, 0,8,19, 85,8,195,\n 83,7,35, 0,8,115, 0,8,51, 0,9,198,\n 81,7,11, 0,8,99, 0,8,35, 0,9,166,\n 0,8,3, 0,8,131, 0,8,67, 0,9,230,\n 80,7,7, 0,8,91, 0,8,27, 0,9,150,\n 84,7,67, 0,8,123, 0,8,59, 0,9,214,\n 82,7,19, 0,8,107, 0,8,43, 0,9,182,\n 0,8,11, 0,8,139, 0,8,75, 0,9,246,\n 80,7,5, 0,8,87, 0,8,23, 192,8,0,\n 83,7,51, 0,8,119, 0,8,55, 0,9,206,\n 81,7,15, 0,8,103, 0,8,39, 0,9,174,\n 0,8,7, 0,8,135, 0,8,71, 0,9,238,\n 80,7,9, 0,8,95, 0,8,31, 0,9,158,\n 84,7,99, 0,8,127, 0,8,63, 0,9,222,\n 82,7,27, 0,8,111, 0,8,47, 0,9,190,\n 0,8,15, 0,8,143, 0,8,79, 0,9,254,\n 96,7,256, 0,8,80, 0,8,16, 84,8,115,\n 82,7,31, 0,8,112, 0,8,48, 0,9,193,\n\n 80,7,10, 0,8,96, 0,8,32, 0,9,161,\n 0,8,0, 0,8,128, 0,8,64, 0,9,225,\n 80,7,6, 0,8,88, 0,8,24, 0,9,145,\n 83,7,59, 0,8,120, 0,8,56, 0,9,209,\n 81,7,17, 0,8,104, 0,8,40, 0,9,177,\n 0,8,8, 0,8,136, 0,8,72, 0,9,241,\n 80,7,4, 0,8,84, 0,8,20, 85,8,227,\n 83,7,43, 0,8,116, 0,8,52, 0,9,201,\n 81,7,13, 0,8,100, 0,8,36, 0,9,169,\n 0,8,4, 0,8,132, 0,8,68, 0,9,233,\n 80,7,8, 0,8,92, 0,8,28, 0,9,153,\n 84,7,83, 0,8,124, 0,8,60, 0,9,217,\n 82,7,23, 0,8,108, 0,8,44, 0,9,185,\n 0,8,12, 0,8,140, 0,8,76, 0,9,249,\n 80,7,3, 0,8,82, 0,8,18, 85,8,163,\n 83,7,35, 0,8,114, 0,8,50, 0,9,197,\n 81,7,11, 0,8,98, 0,8,34, 0,9,165,\n 0,8,2, 0,8,130, 0,8,66, 0,9,229,\n 80,7,7, 0,8,90, 0,8,26, 0,9,149,\n 84,7,67, 0,8,122, 0,8,58, 0,9,213,\n 82,7,19, 0,8,106, 0,8,42, 0,9,181,\n 0,8,10, 0,8,138, 0,8,74, 0,9,245,\n 80,7,5, 0,8,86, 0,8,22, 192,8,0,\n 83,7,51, 0,8,118, 0,8,54, 0,9,205,\n 81,7,15, 0,8,102, 0,8,38, 0,9,173,\n 0,8,6, 0,8,134, 0,8,70, 0,9,237,\n 80,7,9, 0,8,94, 0,8,30, 0,9,157,\n 84,7,99, 0,8,126, 0,8,62, 0,9,221,\n 82,7,27, 0,8,110, 0,8,46, 0,9,189,\n 0,8,14, 0,8,142, 0,8,78, 0,9,253,\n 96,7,256, 0,8,81, 0,8,17, 85,8,131,\n 82,7,31, 0,8,113, 0,8,49, 0,9,195,\n 80,7,10, 0,8,97, 0,8,33, 0,9,163,\n 0,8,1, 0,8,129, 0,8,65, 0,9,227,\n 80,7,6, 0,8,89, 0,8,25, 0,9,147,\n 83,7,59, 0,8,121, 0,8,57, 0,9,211,\n 81,7,17, 0,8,105, 0,8,41, 0,9,179,\n 0,8,9, 0,8,137, 0,8,73, 0,9,243,\n 80,7,4, 0,8,85, 0,8,21, 80,8,258,\n 83,7,43, 0,8,117, 0,8,53, 0,9,203,\n 81,7,13, 0,8,101, 0,8,37, 0,9,171,\n 0,8,5, 0,8,133, 0,8,69, 0,9,235,\n 80,7,8, 0,8,93, 0,8,29, 0,9,155,\n 84,7,83, 0,8,125, 0,8,61, 0,9,219,\n 82,7,23, 0,8,109, 0,8,45, 0,9,187,\n 0,8,13, 0,8,141, 0,8,77, 0,9,251,\n 80,7,3, 0,8,83, 0,8,19, 85,8,195,\n 83,7,35, 0,8,115, 0,8,51, 0,9,199,\n 81,7,11, 0,8,99, 0,8,35, 0,9,167,\n 0,8,3, 0,8,131, 0,8,67, 0,9,231,\n 80,7,7, 0,8,91, 0,8,27, 0,9,151,\n 84,7,67, 0,8,123, 0,8,59, 0,9,215,\n 82,7,19, 0,8,107, 0,8,43, 0,9,183,\n 0,8,11, 0,8,139, 0,8,75, 0,9,247,\n 80,7,5, 0,8,87, 0,8,23, 192,8,0,\n 83,7,51, 0,8,119, 0,8,55, 0,9,207,\n 81,7,15, 0,8,103, 0,8,39, 0,9,175,\n 0,8,7, 0,8,135, 0,8,71, 0,9,239,\n 80,7,9, 0,8,95, 0,8,31, 0,9,159,\n 84,7,99, 0,8,127, 0,8,63, 0,9,223,\n 82,7,27, 0,8,111, 0,8,47, 0,9,191,\n 0,8,15, 0,8,143, 0,8,79, 0,9,255\n];\nvar fixed_td = [\n 80,5,1, 87,5,257, 83,5,17, 91,5,4097,\n 81,5,5, 89,5,1025, 85,5,65, 93,5,16385,\n 80,5,3, 88,5,513, 84,5,33, 92,5,8193,\n 82,5,9, 90,5,2049, 86,5,129, 192,5,24577,\n 80,5,2, 87,5,385, 83,5,25, 91,5,6145,\n 81,5,7, 89,5,1537, 85,5,97, 93,5,24577,\n 80,5,4, 88,5,769, 84,5,49, 92,5,12289,\n 82,5,13, 90,5,3073, 86,5,193, 192,5,24577\n];\n\n // Tables for deflate from PKZIP's appnote.txt.\n var cplens = [ // Copy lengths for literal codes 257..285\n 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,\n 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0\n ];\n\n // see note #13 above about 258\n var cplext = [ // Extra bits for literal codes 257..285\n 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,\n 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid\n ];\n\n var cpdist = [ // Copy offsets for distance codes 0..29\n 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,\n 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,\n 8193, 12289, 16385, 24577\n ];\n\n var cpdext = [ // Extra bits for distance codes\n 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,\n 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,\n 12, 12, 13, 13];\n\n//\n// ZStream.java\n//\n\nfunction ZStream() {\n}\n\n\nZStream.prototype.inflateInit = function(w, nowrap) {\n if (!w) {\n\tw = DEF_WBITS;\n }\n if (nowrap) {\n\tnowrap = false;\n }\n this.istate = new Inflate();\n return this.istate.inflateInit(this, nowrap?-w:w);\n}\n\nZStream.prototype.inflate = function(f) {\n if(this.istate==null) return Z_STREAM_ERROR;\n return this.istate.inflate(this, f);\n}\n\nZStream.prototype.inflateEnd = function(){\n if(this.istate==null) return Z_STREAM_ERROR;\n var ret=istate.inflateEnd(this);\n this.istate = null;\n return ret;\n}\nZStream.prototype.inflateSync = function(){\n // if(istate == null) return Z_STREAM_ERROR;\n return istate.inflateSync(this);\n}\nZStream.prototype.inflateSetDictionary = function(dictionary, dictLength){\n // if(istate == null) return Z_STREAM_ERROR;\n return istate.inflateSetDictionary(this, dictionary, dictLength);\n}\n\n/*\n\n public int deflateInit(int level){\n return deflateInit(level, MAX_WBITS);\n }\n public int deflateInit(int level, boolean nowrap){\n return deflateInit(level, MAX_WBITS, nowrap);\n }\n public int deflateInit(int level, int bits){\n return deflateInit(level, bits, false);\n }\n public int deflateInit(int level, int bits, boolean nowrap){\n dstate=new Deflate();\n return dstate.deflateInit(this, level, nowrap?-bits:bits);\n }\n public int deflate(int flush){\n if(dstate==null){\n return Z_STREAM_ERROR;\n }\n return dstate.deflate(this, flush);\n }\n public int deflateEnd(){\n if(dstate==null) return Z_STREAM_ERROR;\n int ret=dstate.deflateEnd();\n dstate=null;\n return ret;\n }\n public int deflateParams(int level, int strategy){\n if(dstate==null) return Z_STREAM_ERROR;\n return dstate.deflateParams(this, level, strategy);\n }\n public int deflateSetDictionary (byte[] dictionary, int dictLength){\n if(dstate == null)\n return Z_STREAM_ERROR;\n return dstate.deflateSetDictionary(this, dictionary, dictLength);\n }\n\n*/\n\n/*\n // Flush as much pending output as possible. All deflate() output goes\n // through this function so some applications may wish to modify it\n // to avoid allocating a large strm->next_out buffer and copying into it.\n // (See also read_buf()).\n void flush_pending(){\n int len=dstate.pending;\n\n if(len>avail_out) len=avail_out;\n if(len==0) return;\n\n if(dstate.pending_buf.length<=dstate.pending_out ||\n next_out.length<=next_out_index ||\n dstate.pending_buf.length<(dstate.pending_out+len) ||\n next_out.length<(next_out_index+len)){\n System.out.println(dstate.pending_buf.length+\", \"+dstate.pending_out+\n\t\t\t \", \"+next_out.length+\", \"+next_out_index+\", \"+len);\n System.out.println(\"avail_out=\"+avail_out);\n }\n\n System.arraycopy(dstate.pending_buf, dstate.pending_out,\n\t\t next_out, next_out_index, len);\n\n next_out_index+=len;\n dstate.pending_out+=len;\n total_out+=len;\n avail_out-=len;\n dstate.pending-=len;\n if(dstate.pending==0){\n dstate.pending_out=0;\n }\n }\n\n // Read a new buffer from the current input stream, update the adler32\n // and total number of bytes read. All deflate() input goes through\n // this function so some applications may wish to modify it to avoid\n // allocating a large strm->next_in buffer and copying from it.\n // (See also flush_pending()).\n int read_buf(byte[] buf, int start, int size) {\n int len=avail_in;\n\n if(len>size) len=size;\n if(len==0) return 0;\n\n avail_in-=len;\n\n if(dstate.noheader==0) {\n adler=_adler.adler32(adler, next_in, next_in_index, len);\n }\n System.arraycopy(next_in, next_in_index, buf, start, len);\n next_in_index += len;\n total_in += len;\n return len;\n }\n\n public void free(){\n next_in=null;\n next_out=null;\n msg=null;\n _adler=null;\n }\n}\n*/\n\n\n//\n// Inflate.java\n//\n\nfunction Inflate() {\n this.was = [0];\n}\n\nInflate.prototype.inflateReset = function(z) {\n if(z == null || z.istate == null) return Z_STREAM_ERROR;\n \n z.total_in = z.total_out = 0;\n z.msg = null;\n z.istate.mode = z.istate.nowrap!=0 ? BLOCKS : METHOD;\n z.istate.blocks.reset(z, null);\n return Z_OK;\n}\n\nInflate.prototype.inflateEnd = function(z){\n if(this.blocks != null)\n this.blocks.free(z);\n this.blocks=null;\n return Z_OK;\n}\n\nInflate.prototype.inflateInit = function(z, w){\n z.msg = null;\n this.blocks = null;\n\n // handle undocumented nowrap option (no zlib header or check)\n nowrap = 0;\n if(w < 0){\n w = - w;\n nowrap = 1;\n }\n\n // set window size\n if(w<8 ||w>15){\n this.inflateEnd(z);\n return Z_STREAM_ERROR;\n }\n this.wbits=w;\n\n z.istate.blocks=new InfBlocks(z, \n\t\t\t\t z.istate.nowrap!=0 ? null : this,\n\t\t\t\t 1<>4)+8>z.istate.wbits){\n z.istate.mode = BAD;\n z.msg=\"invalid window size\";\n z.istate.marker = 5; // can't try inflateSync\n break;\n }\n z.istate.mode=FLAG;\n case FLAG:\n\n if(z.avail_in==0)return r;r=f;\n\n z.avail_in--; z.total_in++;\n b = (z.next_in[z.next_in_index++])&0xff;\n\n if((((z.istate.method << 8)+b) % 31)!=0){\n z.istate.mode = BAD;\n z.msg = \"incorrect header check\";\n z.istate.marker = 5; // can't try inflateSync\n break;\n }\n\n if((b&PRESET_DICT)==0){\n z.istate.mode = BLOCKS;\n break;\n }\n z.istate.mode = DICT4;\n case DICT4:\n\n if(z.avail_in==0)return r;r=f;\n\n z.avail_in--; z.total_in++;\n z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000;\n z.istate.mode=DICT3;\n case DICT3:\n\n if(z.avail_in==0)return r;r=f;\n\n z.avail_in--; z.total_in++;\n z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000;\n z.istate.mode=DICT2;\n case DICT2:\n\n if(z.avail_in==0)return r;r=f;\n\n z.avail_in--; z.total_in++;\n z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00;\n z.istate.mode=DICT1;\n case DICT1:\n\n if(z.avail_in==0)return r;r=f;\n\n z.avail_in--; z.total_in++;\n z.istate.need += (z.next_in[z.next_in_index++]&0xff);\n z.adler = z.istate.need;\n z.istate.mode = DICT0;\n return Z_NEED_DICT;\n case DICT0:\n z.istate.mode = BAD;\n z.msg = \"need dictionary\";\n z.istate.marker = 0; // can try inflateSync\n return Z_STREAM_ERROR;\n case BLOCKS:\n\n r = z.istate.blocks.proc(z, r);\n if(r == Z_DATA_ERROR){\n z.istate.mode = BAD;\n z.istate.marker = 0; // can try inflateSync\n break;\n }\n if(r == Z_OK){\n r = f;\n }\n if(r != Z_STREAM_END){\n return r;\n }\n r = f;\n z.istate.blocks.reset(z, z.istate.was);\n if(z.istate.nowrap!=0){\n z.istate.mode=DONE;\n break;\n }\n z.istate.mode=CHECK4;\n case CHECK4:\n\n if(z.avail_in==0)return r;r=f;\n\n z.avail_in--; z.total_in++;\n z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000;\n z.istate.mode=CHECK3;\n case CHECK3:\n\n if(z.avail_in==0)return r;r=f;\n\n z.avail_in--; z.total_in++;\n z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000;\n z.istate.mode = CHECK2;\n case CHECK2:\n\n if(z.avail_in==0)return r;r=f;\n\n z.avail_in--; z.total_in++;\n z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00;\n z.istate.mode = CHECK1;\n case CHECK1:\n\n if(z.avail_in==0)return r;r=f;\n\n z.avail_in--; z.total_in++;\n z.istate.need+=(z.next_in[z.next_in_index++]&0xff);\n\n if(((z.istate.was[0])) != ((z.istate.need))){\n z.istate.mode = BAD;\n z.msg = \"incorrect data check\";\n z.istate.marker = 5; // can't try inflateSync\n break;\n }\n\n z.istate.mode = DONE;\n case DONE:\n return Z_STREAM_END;\n case BAD:\n return Z_DATA_ERROR;\n default:\n return Z_STREAM_ERROR;\n }\n }\n }\n\n\nInflate.prototype.inflateSetDictionary = function(z, dictionary, dictLength) {\n var index=0;\n var length = dictLength;\n if(z==null || z.istate == null|| z.istate.mode != DICT0)\n return Z_STREAM_ERROR;\n\n if(z._adler.adler32(1, dictionary, 0, dictLength)!=z.adler){\n return Z_DATA_ERROR;\n }\n\n z.adler = z._adler.adler32(0, null, 0, 0);\n\n if(length >= (1<>> 1){\n case 0: // stored \n {b>>>=(3);k-=(3);}\n t = k & 7; // go to byte boundary\n\n {b>>>=(t);k-=(t);}\n this.mode = IB_LENS; // get length of stored block\n break;\n case 1: // fixed\n {\n var bl=new Int32Array(1);\n\t var bd=new Int32Array(1);\n var tl=[];\n\t var td=[];\n\n\t inflate_trees_fixed(bl, bd, tl, td, z);\n this.codes.init(bl[0], bd[0], tl[0], 0, td[0], 0, z);\n }\n\n {b>>>=(3);k-=(3);}\n\n this.mode = IB_CODES;\n break;\n case 2: // dynamic\n\n {b>>>=(3);k-=(3);}\n\n this.mode = IB_TABLE;\n break;\n case 3: // illegal\n\n {b>>>=(3);k-=(3);}\n this.mode = BAD;\n z.msg = \"invalid block type\";\n r = Z_DATA_ERROR;\n\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z,r);\n\t}\n\tbreak;\n case IB_LENS:\n\twhile(k<(32)){\n\t if(n!=0){\n\t r=Z_OK;\n\t }\n\t else{\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;\n\t z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z,r);\n\t };\n\t n--;\n\t b|=(z.next_in[p++]&0xff)<>> 16) & 0xffff) != (b & 0xffff)){\n\t this.mode = BAD;\n\t z.msg = \"invalid stored block lengths\";\n\t r = Z_DATA_ERROR;\n\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z,r);\n\t}\n\tthis.left = (b & 0xffff);\n\tb = k = 0; // dump bits\n\tthis.mode = this.left!=0 ? IB_STORED : (this.last!=0 ? IB_DRY : IB_TYPE);\n\tbreak;\n case IB_STORED:\n\tif (n == 0){\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t write=q;\n\t return this.inflate_flush(z,r);\n\t}\n\n\tif(m==0){\n\t if(q==end&&read!=0){\n\t q=0; m=(qn) t = n;\n\tif(t>m) t = m;\n\tarrayCopy(z.next_in, p, this.window, q, t);\n\tp += t; n -= t;\n\tq += t; m -= t;\n\tif ((this.left -= t) != 0)\n\t break;\n\tthis.mode = (this.last != 0 ? IB_DRY : IB_TYPE);\n\tbreak;\n case IB_TABLE:\n\n\twhile(k<(14)){\n\t if(n!=0){\n\t r=Z_OK;\n\t }\n\t else{\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;\n\t z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z,r);\n\t };\n\t n--;\n\t b|=(z.next_in[p++]&0xff)< 29 || ((t >> 5) & 0x1f) > 29)\n\t {\n\t this.mode = IB_BAD;\n\t z.msg = \"too many length or distance symbols\";\n\t r = Z_DATA_ERROR;\n\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z,r);\n\t }\n\tt = 258 + (t & 0x1f) + ((t >> 5) & 0x1f);\n\tif(this.blens==null || this.blens.length>>=(14);k-=(14);}\n\n\tthis.index = 0;\n\tmode = IB_BTREE;\n case IB_BTREE:\n\twhile (this.index < 4 + (this.table >>> 10)){\n\t while(k<(3)){\n\t if(n!=0){\n\t r=Z_OK;\n\t }\n\t else{\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;\n\t z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z,r);\n\t };\n\t n--;\n\t b|=(z.next_in[p++]&0xff)<>>=(3);k-=(3);}\n\t}\n\n\twhile(this.index < 19){\n\t this.blens[INFBLOCKS_BORDER[this.index++]] = 0;\n\t}\n\n\tthis.bb[0] = 7;\n\tt = this.inftree.inflate_trees_bits(this.blens, this.bb, this.tb, this.hufts, z);\n\tif (t != Z_OK){\n\t r = t;\n\t if (r == Z_DATA_ERROR){\n\t this.blens=null;\n\t this.mode = IB_BAD;\n\t }\n\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t write=q;\n\t return this.inflate_flush(z,r);\n\t}\n\n\tthis.index = 0;\n\tthis.mode = IB_DTREE;\n case IB_DTREE:\n\twhile (true){\n\t t = this.table;\n\t if(!(this.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))){\n\t break;\n\t }\n\n\t var h; //int[]\n\t var i, j, c;\n\n\t t = this.bb[0];\n\n\t while(k<(t)){\n\t if(n!=0){\n\t r=Z_OK;\n\t }\n\t else{\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;\n\t z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z,r);\n\t };\n\t n--;\n\t b|=(z.next_in[p++]&0xff)<>>=(t);k-=(t);\n\t this.blens[this.index++] = c;\n\t }\n\t else { // c == 16..18\n\t i = c == 18 ? 7 : c - 14;\n\t j = c == 18 ? 11 : 3;\n\n\t while(k<(t+i)){\n\t if(n!=0){\n\t\tr=Z_OK;\n\t }\n\t else{\n\t\tthis.bitb=b; this.bitk=k; \n\t\tz.avail_in=n;\n\t\tz.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t\tthis.write=q;\n\t\treturn this.inflate_flush(z,r);\n\t };\n\t n--;\n\t b|=(z.next_in[p++]&0xff)<>>=(t);k-=(t);\n\n\t j += (b & inflate_mask[i]);\n\n\t b>>>=(i);k-=(i);\n\n\t i = this.index;\n\t t = this.table;\n\t if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) ||\n\t\t(c == 16 && i < 1)){\n\t this.blens=null;\n\t this.mode = IB_BAD;\n\t z.msg = \"invalid bit length repeat\";\n\t r = Z_DATA_ERROR;\n\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z,r);\n\t }\n\n\t c = c == 16 ? this.blens[i-1] : 0;\n\t do{\n\t this.blens[i++] = c;\n\t }\n\t while (--j!=0);\n\t this.index = i;\n\t }\n\t}\n\n\tthis.tb[0]=-1;\n\t{\n\t var bl=new Int32Array(1);\n\t var bd=new Int32Array(1);\n\t var tl=new Int32Array(1);\n\t var td=new Int32Array(1);\n\t bl[0] = 9; // must be <= 9 for lookahead assumptions\n\t bd[0] = 6; // must be <= 9 for lookahead assumptions\n\n\t t = this.table;\n\t t = this.inftree.inflate_trees_dynamic(257 + (t & 0x1f), \n\t\t\t\t\t 1 + ((t >> 5) & 0x1f),\n\t\t\t\t\t this.blens, bl, bd, tl, td, this.hufts, z);\n\n\t if (t != Z_OK){\n\t if (t == Z_DATA_ERROR){\n\t this.blens=null;\n\t this.mode = BAD;\n\t }\n\t r = t;\n\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z,r);\n\t }\n\t this.codes.init(bl[0], bd[0], this.hufts, tl[0], this.hufts, td[0], z);\n\t}\n\tthis.mode = IB_CODES;\n case IB_CODES:\n\tthis.bitb=b; this.bitk=k;\n\tz.avail_in=n; z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\tthis.write=q;\n\n\tif ((r = this.codes.proc(this, z, r)) != Z_STREAM_END){\n\t return this.inflate_flush(z, r);\n\t}\n\tr = Z_OK;\n\tthis.codes.free(z);\n\n\tp=z.next_in_index; n=z.avail_in;b=this.bitb;k=this.bitk;\n\tq=this.write;m = (q < this.read ? this.read-q-1 : this.end-q);\n\n\tif (this.last==0){\n\t this.mode = IB_TYPE;\n\t break;\n\t}\n\tthis.mode = IB_DRY;\n case IB_DRY:\n\tthis.write=q; \n\tr = this.inflate_flush(z, r); \n\tq=this.write; m = (q < this.read ? this.read-q-1 : this.end-q);\n\tif (this.read != this.write){\n\t this.bitb=b; this.bitk=k; \n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t this.write=q;\n\t return this.inflate_flush(z, r);\n\t}\n\tmode = DONE;\n case IB_DONE:\n\tr = Z_STREAM_END;\n\n\tthis.bitb=b; this.bitk=k; \n\tz.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\tthis.write=q;\n\treturn this.inflate_flush(z, r);\n case IB_BAD:\n\tr = Z_DATA_ERROR;\n\n\tthis.bitb=b; this.bitk=k; \n\tz.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\tthis.write=q;\n\treturn this.inflate_flush(z, r);\n\n default:\n\tr = Z_STREAM_ERROR;\n\n\tthis.bitb=b; this.bitk=k; \n\tz.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\tthis.write=q;\n\treturn this.inflate_flush(z, r);\n }\n }\n }\n\nInfBlocks.prototype.free = function(z){\n this.reset(z, null);\n this.window=null;\n this.hufts=null;\n}\n\nInfBlocks.prototype.set_dictionary = function(d, start, n){\n arrayCopy(d, start, window, 0, n);\n this.read = this.write = n;\n}\n\n // Returns true if inflate is currently at the end of a block generated\n // by Z_SYNC_FLUSH or Z_FULL_FLUSH. \nInfBlocks.prototype.sync_point = function(){\n return this.mode == IB_LENS;\n}\n\n // copy as much as possible from the sliding window to the output area\nInfBlocks.prototype.inflate_flush = function(z, r){\n var n;\n var p;\n var q;\n\n // local copies of source and destination pointers\n p = z.next_out_index;\n q = this.read;\n\n // compute number of bytes to copy as far as end of window\n n = ((q <= this.write ? this.write : this.end) - q);\n if (n > z.avail_out) n = z.avail_out;\n if (n!=0 && r == Z_BUF_ERROR) r = Z_OK;\n\n // update counters\n z.avail_out -= n;\n z.total_out += n;\n\n // update check information\n if(this.checkfn != null)\n z.adler=this.check=z._adler.adler32(this.check, this.window, q, n);\n\n // copy as far as end of window\n arrayCopy(this.window, q, z.next_out, p, n);\n p += n;\n q += n;\n\n // see if more to copy at beginning of window\n if (q == this.end){\n // wrap pointers\n q = 0;\n if (this.write == this.end)\n this.write = 0;\n\n // compute bytes to copy\n n = this.write - q;\n if (n > z.avail_out) n = z.avail_out;\n if (n!=0 && r == Z_BUF_ERROR) r = Z_OK;\n\n // update counters\n z.avail_out -= n;\n z.total_out += n;\n\n // update check information\n if(this.checkfn != null)\n\tz.adler=this.check=z._adler.adler32(this.check, this.window, q, n);\n\n // copy\n arrayCopy(this.window, q, z.next_out, p, n);\n p += n;\n q += n;\n }\n\n // update pointers\n z.next_out_index = p;\n this.read = q;\n\n // done\n return r;\n }\n\n//\n// InfCodes.java\n//\n\nvar IC_START=0; // x: set up for LEN\nvar IC_LEN=1; // i: get length/literal/eob next\nvar IC_LENEXT=2; // i: getting length extra (have base)\nvar IC_DIST=3; // i: get distance next\nvar IC_DISTEXT=4;// i: getting distance extra\nvar IC_COPY=5; // o: copying bytes in window, waiting for space\nvar IC_LIT=6; // o: got literal, waiting for output space\nvar IC_WASH=7; // o: got eob, possibly still output waiting\nvar IC_END=8; // x: got eob and all data flushed\nvar IC_BADCODE=9;// x: got error\n\nfunction InfCodes() {\n}\n\nInfCodes.prototype.init = function(bl, bd, tl, tl_index, td, td_index, z) {\n this.mode=IC_START;\n this.lbits=bl;\n this.dbits=bd;\n this.ltree=tl;\n this.ltree_index=tl_index;\n this.dtree = td;\n this.dtree_index=td_index;\n this.tree=null;\n}\n\nInfCodes.prototype.proc = function(s, z, r){ \n var j; // temporary storage\n var t; // temporary pointer (int[])\n var tindex; // temporary pointer\n var e; // extra bits or operation\n var b=0; // bit buffer\n var k=0; // bits in bit buffer\n var p=0; // input data pointer\n var n; // bytes available there\n var q; // output window write pointer\n var m; // bytes to end of window or read pointer\n var f; // pointer to copy strings from\n\n // copy input/output information to locals (UPDATE macro restores)\n p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk;\n q=s.write;m=q= 258 && n >= 10){\n\n\t s.bitb=b;s.bitk=k;\n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t s.write=q;\n\t r = this.inflate_fast(this.lbits, this.dbits, \n\t\t\t this.ltree, this.ltree_index, \n\t\t\t this.dtree, this.dtree_index,\n\t\t\t s, z);\n\n\t p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk;\n\t q=s.write;m=q>>=(this.tree[tindex+1]);\n\tk-=(this.tree[tindex+1]);\n\n\te=this.tree[tindex];\n\n\tif(e == 0){ // literal\n\t this.lit = this.tree[tindex+2];\n\t this.mode = IC_LIT;\n\t break;\n\t}\n\tif((e & 16)!=0 ){ // length\n\t this.get = e & 15;\n\t this.len = this.tree[tindex+2];\n\t this.mode = IC_LENEXT;\n\t break;\n\t}\n\tif ((e & 64) == 0){ // next table\n\t this.need = e;\n\t this.tree_index = tindex/3 + this.tree[tindex+2];\n\t break;\n\t}\n\tif ((e & 32)!=0){ // end of block\n\t this.mode = IC_WASH;\n\t break;\n\t}\n\tthis.mode = IC_BADCODE; // invalid code\n\tz.msg = \"invalid literal/length code\";\n\tr = Z_DATA_ERROR;\n\n\ts.bitb=b;s.bitk=k;\n\tz.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\ts.write=q;\n\treturn s.inflate_flush(z,r);\n\n case IC_LENEXT: // i: getting length extra (have base)\n\tj = this.get;\n\n\twhile(k<(j)){\n\t if(n!=0)r=Z_OK;\n\t else{\n\n\t s.bitb=b;s.bitk=k;\n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t s.write=q;\n\t return s.inflate_flush(z,r);\n\t }\n\t n--; b|=(z.next_in[p++]&0xff)<>=j;\n\tk-=j;\n\n\tthis.need = this.dbits;\n\tthis.tree = this.dtree;\n\tthis.tree_index = this.dtree_index;\n\tthis.mode = IC_DIST;\n case IC_DIST: // i: get distance next\n\tj = this.need;\n\n\twhile(k<(j)){\n\t if(n!=0)r=Z_OK;\n\t else{\n\n\t s.bitb=b;s.bitk=k;\n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t s.write=q;\n\t return s.inflate_flush(z,r);\n\t }\n\t n--; b|=(z.next_in[p++]&0xff)<>=this.tree[tindex+1];\n\tk-=this.tree[tindex+1];\n\n\te = (this.tree[tindex]);\n\tif((e & 16)!=0){ // distance\n\t this.get = e & 15;\n\t this.dist = this.tree[tindex+2];\n\t this.mode = IC_DISTEXT;\n\t break;\n\t}\n\tif ((e & 64) == 0){ // next table\n\t this.need = e;\n\t this.tree_index = tindex/3 + this.tree[tindex+2];\n\t break;\n\t}\n\tthis.mode = IC_BADCODE; // invalid code\n\tz.msg = \"invalid distance code\";\n\tr = Z_DATA_ERROR;\n\n\ts.bitb=b;s.bitk=k;\n\tz.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\ts.write=q;\n\treturn s.inflate_flush(z,r);\n\n case IC_DISTEXT: // i: getting distance extra\n\tj = this.get;\n\n\twhile(k<(j)){\n\t if(n!=0)r=Z_OK;\n\t else{\n\n\t s.bitb=b;s.bitk=k;\n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t s.write=q;\n\t return s.inflate_flush(z,r);\n\t }\n\t n--; b|=(z.next_in[p++]&0xff)<>=j;\n\tk-=j;\n\n\tthis.mode = IC_COPY;\n case IC_COPY: // o: copying bytes in window, waiting for space\n f = q - this.dist;\n while(f < 0){ // modulo window size-\"while\" instead\n f += s.end; // of \"if\" handles invalid distances\n\t}\n\twhile (this.len!=0){\n\n\t if(m==0){\n\t if(q==s.end&&s.read!=0){q=0;m=q 7){ // return unused byte, if any\n\t k -= 8;\n\t n++;\n\t p--; // can always return one\n\t}\n\n\ts.write=q; r=s.inflate_flush(z,r);\n\tq=s.write;m=q= 258 && n >= 10\n // get literal/length code\n while(k<(20)){ // max bits for literal/length code\n\tn--;\n\tb|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]);\n\n\ts.window[q++] = tp[tp_index_t_3+2];\n\tm--;\n\tcontinue;\n }\n do {\n\n\tb>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]);\n\n\tif((e&16)!=0){\n\t e &= 15;\n\t c = tp[tp_index_t_3+2] + (b & inflate_mask[e]);\n\n\t b>>=e; k-=e;\n\n\t // decode distance base of block to copy\n\t while(k<(15)){ // max bits for distance code\n\t n--;\n\t b|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]);\n\n\t if((e&16)!=0){\n\t // get extra bits to add to distance base\n\t e &= 15;\n\t while(k<(e)){ // get extra bits (up to 13)\n\t\tn--;\n\t\tb|=(z.next_in[p++]&0xff)<>=(e); k-=(e);\n\n\t // do the copy\n\t m -= c;\n\t if (q >= d){ // offset before dest\n\t\t// just copy\n\t\tr=q-d;\n\t\tif(q-r>0 && 2>(q-r)){ \n\t\t s.window[q++]=s.window[r++]; // minimum count is three,\n\t\t s.window[q++]=s.window[r++]; // so unroll loop a little\n\t\t c-=2;\n\t\t}\n\t\telse{\n\t\t s.window[q++]=s.window[r++]; // minimum count is three,\n\t\t s.window[q++]=s.window[r++]; // so unroll loop a little\n\t\t c-=2;\n\t\t}\n\t }\n\t else{ // else offset after destination\n r=q-d;\n do{\n r+=s.end; // force pointer in window\n }while(r<0); // covers invalid distances\n\t\te=s.end-r;\n\t\tif(c>e){ // if source crosses,\n\t\t c-=e; // wrapped copy\n\t\t if(q-r>0 && e>(q-r)){ \n\t\t do{s.window[q++] = s.window[r++];}\n\t\t while(--e!=0);\n\t\t }\n\t\t else{\n\t\t arrayCopy(s.window, r, s.window, q, e);\n\t\t q+=e; r+=e; e=0;\n\t\t }\n\t\t r = 0; // copy rest from start of window\n\t\t}\n\n\t }\n\n\t // copy all or what's left\n do{s.window[q++] = s.window[r++];}\n\t\twhile(--c!=0);\n\t break;\n\t }\n\t else if((e&64)==0){\n\t t+=tp[tp_index_t_3+2];\n\t t+=(b&inflate_mask[e]);\n\t tp_index_t_3=(tp_index+t)*3;\n\t e=tp[tp_index_t_3];\n\t }\n\t else{\n\t z.msg = \"invalid distance code\";\n\n\t c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3;\n\n\t s.bitb=b;s.bitk=k;\n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t s.write=q;\n\n\t return Z_DATA_ERROR;\n\t }\n\t }\n\t while(true);\n\t break;\n\t}\n\n\tif((e&64)==0){\n\t t+=tp[tp_index_t_3+2];\n\t t+=(b&inflate_mask[e]);\n\t tp_index_t_3=(tp_index+t)*3;\n\t if((e=tp[tp_index_t_3])==0){\n\n\t b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]);\n\n\t s.window[q++]=tp[tp_index_t_3+2];\n\t m--;\n\t break;\n\t }\n\t}\n\telse if((e&32)!=0){\n\n\t c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3;\n \n\t s.bitb=b;s.bitk=k;\n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t s.write=q;\n\n\t return Z_STREAM_END;\n\t}\n\telse{\n\t z.msg=\"invalid literal/length code\";\n\n\t c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3;\n\n\t s.bitb=b;s.bitk=k;\n\t z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n\t s.write=q;\n\n\t return Z_DATA_ERROR;\n\t}\n } \n while(true);\n } \n while(m>=258 && n>= 10);\n\n // not enough input or output--restore pointers and return\n c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3;\n\n s.bitb=b;s.bitk=k;\n z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p;\n s.write=q;\n\n return Z_OK;\n}\n\n//\n// InfTree.java\n//\n\nfunction InfTree() {\n}\n\nInfTree.prototype.huft_build = function(b, bindex, n, s, d, e, t, m, hp, hn, v) {\n\n // Given a list of code lengths and a maximum table size, make a set of\n // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR\n // if the given code set is incomplete (the tables are still built in this\n // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of\n // lengths), or Z_MEM_ERROR if not enough memory.\n\n var a; // counter for codes of length k\n var f; // i repeats in table every f entries\n var g; // maximum code length\n var h; // table level\n var i; // counter, current code\n var j; // counter\n var k; // number of bits in current code\n var l; // bits per table (returned in m)\n var mask; // (1 << w) - 1, to avoid cc -O bug on HP\n var p; // pointer into c[], b[], or v[]\n var q; // points to current table\n var w; // bits before this table == (l * h)\n var xp; // pointer into x\n var y; // number of dummy codes added\n var z; // number of entries in current table\n\n // Generate counts for each bit length\n\n p = 0; i = n;\n do {\n this.c[b[bindex+p]]++; p++; i--; // assume all entries <= BMAX\n }while(i!=0);\n\n if(this.c[0] == n){ // null input--all zero length codes\n t[0] = -1;\n m[0] = 0;\n return Z_OK;\n }\n\n // Find minimum and maximum length, bound *m by those\n l = m[0];\n for (j = 1; j <= BMAX; j++)\n if(this.c[j]!=0) break;\n k = j; // minimum code length\n if(l < j){\n l = j;\n }\n for (i = BMAX; i!=0; i--){\n if(this.c[i]!=0) break;\n }\n g = i; // maximum code length\n if(l > i){\n l = i;\n }\n m[0] = l;\n\n // Adjust last length count to fill out codes, if needed\n for (y = 1 << j; j < i; j++, y <<= 1){\n if ((y -= this.c[j]) < 0){\n return Z_DATA_ERROR;\n }\n }\n if ((y -= this.c[i]) < 0){\n return Z_DATA_ERROR;\n }\n this.c[i] += y;\n\n // Generate starting offsets into the value table for each length\n this.x[1] = j = 0;\n p = 1; xp = 2;\n while (--i!=0) { // note that i == g from above\n this.x[xp] = (j += this.c[p]);\n xp++;\n p++;\n }\n\n // Make a table of values in order of bit lengths\n i = 0; p = 0;\n do {\n if ((j = b[bindex+p]) != 0){\n this.v[this.x[j]++] = i;\n }\n p++;\n }\n while (++i < n);\n n = this.x[g]; // set n to length of v\n\n // Generate the Huffman codes and for each, make the table entries\n this.x[0] = i = 0; // first Huffman code is zero\n p = 0; // grab values in bit order\n h = -1; // no tables yet--level -1\n w = -l; // bits decoded == (l * h)\n this.u[0] = 0; // just to keep compilers happy\n q = 0; // ditto\n z = 0; // ditto\n\n // go through the bit lengths (k already is bits in shortest code)\n for (; k <= g; k++){\n a = this.c[k];\n while (a--!=0){\n\t// here i is the Huffman code of length k bits for value *p\n\t// make tables up to required level\n while (k > w + l){\n h++;\n w += l; // previous table always l bits\n\t // compute minimum size table less than or equal to l bits\n z = g - w;\n z = (z > l) ? l : z; // table size upper limit\n if((f=1<<(j=k-w))>a+1){ // try a k-w bit table\n // too few codes for k-w bit table\n f -= a + 1; // deduct codes from patterns left\n xp = k;\n if(j < z){\n while (++j < z){ // try smaller tables up to z bits\n if((f <<= 1) <= this.c[++xp])\n break; // enough codes to use up j bits\n f -= this.c[xp]; // else deduct codes from patterns\n }\n\t }\n }\n z = 1 << j; // table entries for j-bit table\n\n\t // allocate new table\n if (this.hn[0] + z > MANY){ // (note: doesn't matter for fixed)\n return Z_DATA_ERROR; // overflow of MANY\n }\n this.u[h] = q = /*hp+*/ this.hn[0]; // DEBUG\n this.hn[0] += z;\n \n\t // connect to last table, if there is one\n\t if(h!=0){\n this.x[h]=i; // save pattern for backing up\n this.r[0]=j; // bits in this table\n this.r[1]=l; // bits to dump before this table\n j=i>>>(w - l);\n this.r[2] = (q - this.u[h-1] - j); // offset to this table\n arrayCopy(this.r, 0, hp, (this.u[h-1]+j)*3, 3); // connect to last table\n }\n else{\n t[0] = q; // first table is returned result\n\t }\n }\n\n\t// set up table entry in r\n this.r[1] = (k - w);\n if (p >= n){\n this.r[0] = 128 + 64; // out of values--invalid code\n\t}\n else if (v[p] < s){\n this.r[0] = (this.v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block\n this.r[2] = this.v[p++]; // simple code is just the value\n }\n else{\n this.r[0]=(e[this.v[p]-s]+16+64); // non-simple--look up in lists\n this.r[2]=d[this.v[p++] - s];\n }\n\n // fill code-like entries with r\n f=1<<(k-w);\n for (j=i>>>w;j>>= 1){\n i ^= j;\n\t}\n i ^= j;\n\n\t// backup over finished tables\n mask = (1 << w) - 1; // needed on HP, cc -O bug\n while ((i & mask) != this.x[h]){\n h--; // don't need to update q\n w -= l;\n mask = (1 << w) - 1;\n }\n }\n }\n // Return Z_BUF_ERROR if we were given an incomplete table\n return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK;\n}\n\nInfTree.prototype.inflate_trees_bits = function(c, bb, tb, hp, z) {\n var result;\n this.initWorkArea(19);\n this.hn[0]=0;\n result = this.huft_build(c, 0, 19, 19, null, null, tb, bb, hp, this.hn, this.v);\n\n if(result == Z_DATA_ERROR){\n z.msg = \"oversubscribed dynamic bit lengths tree\";\n }\n else if(result == Z_BUF_ERROR || bb[0] == 0){\n z.msg = \"incomplete dynamic bit lengths tree\";\n result = Z_DATA_ERROR;\n }\n return result;\n}\n\nInfTree.prototype.inflate_trees_dynamic = function(nl, nd, c, bl, bd, tl, td, hp, z) {\n var result;\n\n // build literal/length tree\n this.initWorkArea(288);\n this.hn[0]=0;\n result = this.huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, this.hn, this.v);\n if (result != Z_OK || bl[0] == 0){\n if(result == Z_DATA_ERROR){\n z.msg = \"oversubscribed literal/length tree\";\n }\n else if (result != Z_MEM_ERROR){\n z.msg = \"incomplete literal/length tree\";\n result = Z_DATA_ERROR;\n }\n return result;\n }\n\n // build distance tree\n this.initWorkArea(288);\n result = this.huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, this.hn, this.v);\n\n if (result != Z_OK || (bd[0] == 0 && nl > 257)){\n if (result == Z_DATA_ERROR){\n z.msg = \"oversubscribed distance tree\";\n }\n else if (result == Z_BUF_ERROR) {\n z.msg = \"incomplete distance tree\";\n result = Z_DATA_ERROR;\n }\n else if (result != Z_MEM_ERROR){\n z.msg = \"empty distance tree with lengths\";\n result = Z_DATA_ERROR;\n }\n return result;\n }\n\n return Z_OK;\n}\n/*\n static int inflate_trees_fixed(int[] bl, //literal desired/actual bit depth\n int[] bd, //distance desired/actual bit depth\n int[][] tl,//literal/length tree result\n int[][] td,//distance tree result \n ZStream z //for memory allocation\n\t\t\t\t ){\n\n*/\n\nfunction inflate_trees_fixed(bl, bd, tl, td, z) {\n bl[0]=fixed_bl;\n bd[0]=fixed_bd;\n tl[0]=fixed_tl;\n td[0]=fixed_td;\n return Z_OK;\n}\n\nInfTree.prototype.initWorkArea = function(vsize){\n if(this.hn==null){\n this.hn=new Int32Array(1);\n this.v=new Int32Array(vsize);\n this.c=new Int32Array(BMAX+1);\n this.r=new Int32Array(3);\n this.u=new Int32Array(BMAX);\n this.x=new Int32Array(BMAX+1);\n }\n if(this.v.length 100) {\n arrayCopy_fast(new Uint8Array(src.buffer, src.byteOffset + srcOffset, count), dest, destOffset);\n } else { \n arrayCopy_slow(src, srcOffset, dest, destOffset, count);\n }\n\n}\n\nfunction arrayCopy_slow(src, srcOffset, dest, destOffset, count) {\n\n // dlog('_slow call: srcOffset=' + srcOffset + '; destOffset=' + destOffset + '; count=' + count);\n\n for (var i = 0; i < count; ++i) {\n dest[destOffset + i] = src[srcOffset + i];\n }\n}\n\nfunction arrayCopy_fast(src, dest, destOffset) {\n dest.set(src, destOffset);\n}\n\n\n // largest prime smaller than 65536\nvar ADLER_BASE=65521; \n // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1\nvar ADLER_NMAX=5552;\n\nfunction adler32(adler, /* byte[] */ buf, index, len){\n if(buf == null){ return 1; }\n\n var s1=adler&0xffff;\n var s2=(adler>>16)&0xffff;\n var k;\n\n while(len > 0) {\n k=len=16){\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n s1+=buf[index++]&0xff; s2+=s1;\n k-=16;\n }\n if(k!=0){\n do{\n s1+=buf[index++]&0xff; s2+=s1;\n }\n while(--k!=0);\n }\n s1%=ADLER_BASE;\n s2%=ADLER_BASE;\n }\n return (s2<<16)|s1;\n}\n\n\n\nfunction jszlib_inflate_buffer(buffer, start, length, afterUncOffset) {\n if (!start) {\n buffer = new Uint8Array(buffer);\n } else if (!length) {\n buffer = new Uint8Array(buffer, start, buffer.byteLength - start);\n } else {\n buffer = new Uint8Array(buffer, start, length);\n }\n\n var z = new ZStream();\n z.inflateInit(DEF_WBITS, true);\n z.next_in = buffer;\n z.next_in_index = 0;\n z.avail_in = buffer.length;\n\n var oBlockList = [];\n var totalSize = 0;\n while (true) {\n var obuf = new Uint8Array(32000);\n z.next_out = obuf;\n z.next_out_index = 0;\n z.avail_out = obuf.length;\n var status = z.inflate(Z_NO_FLUSH);\n if (status != Z_OK && status != Z_STREAM_END && status != Z_BUF_ERROR) {\n throw z.msg;\n }\n if (z.avail_out != 0) {\n var newob = new Uint8Array(obuf.length - z.avail_out);\n arrayCopy(obuf, 0, newob, 0, (obuf.length - z.avail_out));\n obuf = newob;\n }\n oBlockList.push(obuf);\n totalSize += obuf.length;\n if (status == Z_STREAM_END || status == Z_BUF_ERROR) {\n break;\n }\n }\n\n if (afterUncOffset) {\n afterUncOffset[0] = (start || 0) + z.next_in_index;\n }\n\n if (oBlockList.length == 1) {\n return oBlockList[0].buffer;\n } else {\n var out = new Uint8Array(totalSize);\n var cursor = 0;\n for (var i = 0; i < oBlockList.length; ++i) {\n var b = oBlockList[i];\n arrayCopy(b, 0, out, cursor, b.length);\n cursor += b.length;\n }\n return out.buffer;\n }\n}\n\nif (typeof(module) !== 'undefined') {\n module.exports = {\n inflateBuffer: jszlib_inflate_buffer,\n arrayCopy: arrayCopy\n };\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/*\r\n * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined\r\n * in FIPS 180-1\r\n * Version 2.2 Copyright Paul Johnston 2000 - 2009.\r\n * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet\r\n * Distributed under the BSD License\r\n * See http://pajhome.org.uk/crypt/md5 for details.\r\n */\r\n\r\n/*\r\n * Configurable variables. You may need to tweak these to be compatible with\r\n * the server-side, but the defaults work in most cases.\r\n */\r\nvar hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */\r\nvar b64pad = \"\"; /* base-64 pad character. \"=\" for strict RFC compliance */\r\n\r\n/*\r\n * These are the functions you'll usually want to call\r\n * They take string arguments and return either hex or base-64 encoded strings\r\n */\r\nfunction hex_sha1(s) { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); }\r\nfunction b64_sha1(s) { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); }\r\nfunction any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); }\r\nfunction hex_hmac_sha1(k, d)\r\n { return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }\r\nfunction b64_hmac_sha1(k, d)\r\n { return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }\r\nfunction any_hmac_sha1(k, d, e)\r\n { return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); }\r\n\r\n/*\r\n * Perform a simple self-test to see if the VM is working\r\n */\r\nfunction sha1_vm_test()\r\n{\r\n return hex_sha1(\"abc\").toLowerCase() == \"a9993e364706816aba3e25717850c26c9cd0d89d\";\r\n}\r\n\r\n/*\r\n * Calculate the SHA1 of a raw string\r\n */\r\nfunction rstr_sha1(s)\r\n{\r\n return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8));\r\n}\r\n\r\n/*\r\n * Calculate the HMAC-SHA1 of a key and some data (raw strings)\r\n */\r\nfunction rstr_hmac_sha1(key, data)\r\n{\r\n var bkey = rstr2binb(key);\r\n if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8);\r\n\r\n var ipad = Array(16), opad = Array(16);\r\n for(var i = 0; i < 16; i++)\r\n {\r\n ipad[i] = bkey[i] ^ 0x36363636;\r\n opad[i] = bkey[i] ^ 0x5C5C5C5C;\r\n }\r\n\r\n var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8);\r\n return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160));\r\n}\r\n\r\n/*\r\n * Convert a raw string to a hex string\r\n */\r\nfunction rstr2hex(input)\r\n{\r\n // try { hexcase } catch(e) { hexcase=0; }\r\n var hex_tab = hexcase ? \"0123456789ABCDEF\" : \"0123456789abcdef\";\r\n var output = \"\";\r\n var x;\r\n for(var i = 0; i < input.length; i++)\r\n {\r\n x = input.charCodeAt(i);\r\n output += hex_tab.charAt((x >>> 4) & 0x0F)\r\n + hex_tab.charAt( x & 0x0F);\r\n }\r\n return output;\r\n}\r\n\r\n/*\r\n * Convert a raw string to a base-64 string\r\n */\r\nfunction rstr2b64(input)\r\n{\r\n // try { b64pad } catch(e) { b64pad=''; }\r\n var tab = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\r\n var output = \"\";\r\n var len = input.length;\r\n for(var i = 0; i < len; i += 3)\r\n {\r\n var triplet = (input.charCodeAt(i) << 16)\r\n | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)\r\n | (i + 2 < len ? input.charCodeAt(i+2) : 0);\r\n for(var j = 0; j < 4; j++)\r\n {\r\n if(i * 8 + j * 6 > input.length * 8) output += b64pad;\r\n else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);\r\n }\r\n }\r\n return output;\r\n}\r\n\r\n/*\r\n * Convert a raw string to an arbitrary string encoding\r\n */\r\nfunction rstr2any(input, encoding)\r\n{\r\n var divisor = encoding.length;\r\n var remainders = Array();\r\n var i, q, x, quotient;\r\n\r\n /* Convert to an array of 16-bit big-endian values, forming the dividend */\r\n var dividend = Array(Math.ceil(input.length / 2));\r\n for(i = 0; i < dividend.length; i++)\r\n {\r\n dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);\r\n }\r\n\r\n /*\r\n * Repeatedly perform a long division. The binary array forms the dividend,\r\n * the length of the encoding is the divisor. Once computed, the quotient\r\n * forms the dividend for the next step. We stop when the dividend is zero.\r\n * All remainders are stored for later use.\r\n */\r\n while(dividend.length > 0)\r\n {\r\n quotient = Array();\r\n x = 0;\r\n for(i = 0; i < dividend.length; i++)\r\n {\r\n x = (x << 16) + dividend[i];\r\n q = Math.floor(x / divisor);\r\n x -= q * divisor;\r\n if(quotient.length > 0 || q > 0)\r\n quotient[quotient.length] = q;\r\n }\r\n remainders[remainders.length] = x;\r\n dividend = quotient;\r\n }\r\n\r\n /* Convert the remainders to the output string */\r\n var output = \"\";\r\n for(i = remainders.length - 1; i >= 0; i--)\r\n output += encoding.charAt(remainders[i]);\r\n\r\n /* Append leading zero equivalents */\r\n var full_length = Math.ceil(input.length * 8 /\r\n (Math.log(encoding.length) / Math.log(2)))\r\n for(i = output.length; i < full_length; i++)\r\n output = encoding[0] + output;\r\n\r\n return output;\r\n}\r\n\r\n/*\r\n * Encode a string as utf-8.\r\n * For efficiency, this assumes the input is valid utf-16.\r\n */\r\nfunction str2rstr_utf8(input)\r\n{\r\n var output = \"\";\r\n var i = -1;\r\n var x, y;\r\n\r\n while(++i < input.length)\r\n {\r\n /* Decode utf-16 surrogate pairs */\r\n x = input.charCodeAt(i);\r\n y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;\r\n if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)\r\n {\r\n x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);\r\n i++;\r\n }\r\n\r\n /* Encode output as utf-8 */\r\n if(x <= 0x7F)\r\n output += String.fromCharCode(x);\r\n else if(x <= 0x7FF)\r\n output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),\r\n 0x80 | ( x & 0x3F));\r\n else if(x <= 0xFFFF)\r\n output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),\r\n 0x80 | ((x >>> 6 ) & 0x3F),\r\n 0x80 | ( x & 0x3F));\r\n else if(x <= 0x1FFFFF)\r\n output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),\r\n 0x80 | ((x >>> 12) & 0x3F),\r\n 0x80 | ((x >>> 6 ) & 0x3F),\r\n 0x80 | ( x & 0x3F));\r\n }\r\n return output;\r\n}\r\n\r\n/*\r\n * Encode a string as utf-16\r\n */\r\nfunction str2rstr_utf16le(input)\r\n{\r\n var output = \"\";\r\n for(var i = 0; i < input.length; i++)\r\n output += String.fromCharCode( input.charCodeAt(i) & 0xFF,\r\n (input.charCodeAt(i) >>> 8) & 0xFF);\r\n return output;\r\n}\r\n\r\nfunction str2rstr_utf16be(input)\r\n{\r\n var output = \"\";\r\n for(var i = 0; i < input.length; i++)\r\n output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,\r\n input.charCodeAt(i) & 0xFF);\r\n return output;\r\n}\r\n\r\n/*\r\n * Convert a raw string to an array of big-endian words\r\n * Characters >255 have their high-byte silently ignored.\r\n */\r\nfunction rstr2binb(input)\r\n{\r\n var output = Array(input.length >> 2);\r\n for(var i = 0; i < output.length; i++)\r\n output[i] = 0;\r\n for(var i = 0; i < input.length * 8; i += 8)\r\n output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);\r\n return output;\r\n}\r\n\r\n/*\r\n * Convert an array of big-endian words to a string\r\n */\r\nfunction binb2rstr(input)\r\n{\r\n var output = \"\";\r\n for(var i = 0; i < input.length * 32; i += 8)\r\n output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);\r\n return output;\r\n}\r\n\r\n/*\r\n * Calculate the SHA-1 of an array of big-endian words, and a bit length\r\n */\r\nfunction binb_sha1(x, len)\r\n{\r\n /* append padding */\r\n x[len >> 5] |= 0x80 << (24 - len % 32);\r\n x[((len + 64 >> 9) << 4) + 15] = len;\r\n\r\n var w = Array(80);\r\n var a = 1732584193;\r\n var b = -271733879;\r\n var c = -1732584194;\r\n var d = 271733878;\r\n var e = -1009589776;\r\n\r\n for(var i = 0; i < x.length; i += 16)\r\n {\r\n var olda = a;\r\n var oldb = b;\r\n var oldc = c;\r\n var oldd = d;\r\n var olde = e;\r\n\r\n for(var j = 0; j < 80; j++)\r\n {\r\n if(j < 16) w[j] = x[i + j];\r\n else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);\r\n var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)),\r\n safe_add(safe_add(e, w[j]), sha1_kt(j)));\r\n e = d;\r\n d = c;\r\n c = bit_rol(b, 30);\r\n b = a;\r\n a = t;\r\n }\r\n\r\n a = safe_add(a, olda);\r\n b = safe_add(b, oldb);\r\n c = safe_add(c, oldc);\r\n d = safe_add(d, oldd);\r\n e = safe_add(e, olde);\r\n }\r\n return Array(a, b, c, d, e);\r\n\r\n}\r\n\r\n/*\r\n * Perform the appropriate triplet combination function for the current\r\n * iteration\r\n */\r\nfunction sha1_ft(t, b, c, d)\r\n{\r\n if(t < 20) return (b & c) | ((~b) & d);\r\n if(t < 40) return b ^ c ^ d;\r\n if(t < 60) return (b & c) | (b & d) | (c & d);\r\n return b ^ c ^ d;\r\n}\r\n\r\n/*\r\n * Determine the appropriate additive constant for the current iteration\r\n */\r\nfunction sha1_kt(t)\r\n{\r\n return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :\r\n (t < 60) ? -1894007588 : -899497514;\r\n}\r\n\r\n/*\r\n * Add integers, wrapping at 2^32. This uses 16-bit operations internally\r\n * to work around bugs in some JS interpreters.\r\n */\r\nfunction safe_add(x, y)\r\n{\r\n var lsw = (x & 0xFFFF) + (y & 0xFFFF);\r\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\r\n return (msw << 16) | (lsw & 0xFFFF);\r\n}\r\n\r\n/*\r\n * Bitwise rotate a 32-bit number to the left.\r\n */\r\nfunction bit_rol(num, cnt)\r\n{\r\n return (num << cnt) | (num >>> (32 - cnt));\r\n}\r\n\r\nexport { b64_sha1, hex_sha1 };\r\n","/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */\n\n//\n// Dalliance Genome Explorer\n// (c) Thomas Down 2006-2010\n//\n// utils.js: odds, sods, and ends.\n//\n\nimport {b64_sha1} from './sha1';\n\nvar NUM_REGEXP = new RegExp('[0-9]+');\n\nfunction stringToNumbersArray(str) {\n var nums = new Array();\n var m;\n while (m = NUM_REGEXP.exec(str)) {\n nums.push(m[0]);\n str = str.substring(m.index + (m[0].length));\n }\n return nums;\n}\n\nvar STRICT_NUM_REGEXP = new RegExp('^[0-9]+$');\n\nfunction stringToInt(str) {\n str = str.replace(new RegExp(',', 'g'), '');\n if (!STRICT_NUM_REGEXP.test(str)) {\n return null;\n }\n return str | 0;\n}\n\nfunction pusho(obj, k, v) {\n if (obj[k]) {\n obj[k].push(v);\n } else {\n obj[k] = [v];\n }\n}\n\nfunction pushnewo(obj, k, v) {\n var a = obj[k];\n if (a) {\n for (var i = 0; i < a.length; ++i) { // indexOf requires JS16 :-(.\n if (a[i] == v) {\n return;\n }\n }\n a.push(v);\n } else {\n obj[k] = [v];\n }\n}\n\n\nfunction pick(a, b, c, d) {\n if (a) {\n return a;\n } else if (b) {\n return b;\n } else if (c) {\n return c;\n } else if (d) {\n return d;\n }\n}\n\nfunction arrayEquals(a, b) {\n if (!a || !b || a.length !== b.length)\n return false;\n\n for (var i = 0; i < a.length; ++i) {\n if (a[i] !== b[i])\n return false;\n }\n\n return true;\n}\n\nfunction arrayIndexOf(a, x) {\n if (!a) {\n return -1;\n }\n\n for (var i = 0; i < a.length; ++i) {\n if (a[i] === x) {\n return i;\n }\n }\n return -1;\n}\n\nfunction arrayRemove(a, x) {\n var i = arrayIndexOf(a, x);\n if (i >= 0) {\n a.splice(i, 1);\n return true;\n }\n return false;\n}\n\n//\n// DOM utilities\n//\n\n\nfunction makeElement(tag, children, attribs, styles) {\n var ele = document.createElement(tag);\n if (children) {\n if (!(children instanceof Array)) {\n children = [children];\n }\n for (var i = 0; i < children.length; ++i) {\n var c = children[i];\n if (c) {\n if (typeof c == 'string') {\n c = document.createTextNode(c);\n } else if (typeof c == 'number') {\n c = document.createTextNode('' + c);\n }\n ele.appendChild(c);\n }\n }\n }\n\n if (attribs) {\n for (var l in attribs) {\n try {\n ele[l] = attribs[l];\n } catch (e) {\n console.log('error setting ' + l);\n throw(e);\n }\n }\n }\n if (styles) {\n for (var l in styles) {\n ele.style[l] = styles[l];\n }\n }\n return ele;\n}\n\nfunction makeElementNS(namespace, tag, children, attribs) {\n var ele = document.createElementNS(namespace, tag);\n if (children) {\n if (!(children instanceof Array)) {\n children = [children];\n }\n for (var i = 0; i < children.length; ++i) {\n var c = children[i];\n if (typeof c == 'string') {\n c = document.createTextNode(c);\n }\n ele.appendChild(c);\n }\n }\n\n setAttrs(ele, attribs);\n return ele;\n}\n\nvar attr_name_cache = {};\n\nfunction setAttr(node, key, value) {\n var attr = attr_name_cache[key];\n if (!attr) {\n var _attr = '';\n for (var c = 0; c < key.length; ++c) {\n var cc = key.substring(c, c + 1);\n var lcc = cc.toLowerCase();\n if (lcc != cc) {\n _attr = _attr + '-' + lcc;\n } else {\n _attr = _attr + cc;\n }\n }\n attr_name_cache[key] = _attr;\n attr = _attr;\n }\n node.setAttribute(attr, value);\n}\n\nfunction setAttrs(node, attribs) {\n if (attribs) {\n for (var l in attribs) {\n setAttr(node, l, attribs[l]);\n }\n }\n}\n\n\nfunction removeChildren(node) {\n if (!node || !node.childNodes) {\n return;\n }\n\n while (node.childNodes.length > 0) {\n node.removeChild(node.firstChild);\n }\n}\n\n\n//\n// WARNING: not for general use!\n//\n\nfunction miniJSONify(o, exc) {\n if (typeof o === 'undefined') {\n return 'undefined';\n } else if (o == null) {\n return 'null';\n } else if (typeof o == 'string') {\n return \"'\" + o + \"'\";\n } else if (typeof o == 'number') {\n return \"\" + o;\n } else if (typeof o == 'boolean') {\n return \"\" + o;\n } else if (typeof o == 'object') {\n if (o instanceof Array) {\n var s = null;\n for (var i = 0; i < o.length; ++i) {\n s = (s == null ? '' : (s + ', ')) + miniJSONify(o[i], exc);\n }\n return '[' + (s ? s : '') + ']';\n } else {\n exc = exc || {};\n var s = null;\n for (var k in o) {\n if (exc[k])\n continue;\n if (k != undefined && typeof (o[k]) != 'function') {\n s = (s == null ? '' : (s + ', ')) + k + ': ' + miniJSONify(o[k], exc);\n }\n }\n return '{' + (s ? s : '') + '}';\n }\n } else {\n return (typeof o);\n }\n}\n\nfunction shallowCopy(o) {\n var n = {};\n for (var k in o) {\n n[k] = o[k];\n }\n return n;\n}\n\nfunction Observed(x) {\n this.value = x;\n this.listeners = [];\n}\n\nObserved.prototype.addListener = function (f) {\n this.listeners.push(f);\n};\n\nObserved.prototype.addListenerAndFire = function (f) {\n this.listeners.push(f);\n f(this.value);\n};\n\nObserved.prototype.removeListener = function (f) {\n arrayRemove(this.listeners, f);\n};\n\nObserved.prototype.get = function () {\n return this.value;\n};\n\nObserved.prototype.set = function (x) {\n this.value = x;\n for (var i = 0; i < this.listeners.length; ++i) {\n this.listeners[i](x);\n }\n};\n\nfunction Awaited() {\n this.queue = [];\n}\n\nAwaited.prototype.provide = function (x) {\n if (this.res !== undefined) {\n throw \"Resource has already been provided.\";\n }\n\n this.res = x;\n for (var i = 0; i < this.queue.length; ++i) {\n this.queue[i](x);\n }\n this.queue = null; // avoid leaking closures.\n};\n\nAwaited.prototype.await = function (f) {\n if (this.res !== undefined) {\n f(this.res);\n return this.res;\n } else {\n this.queue.push(f);\n }\n};\n\nvar __dalliance_saltSeed = 0;\n\nfunction saltURL(url) {\n return url + '?salt=' + b64_sha1('' + Date.now() + ',' + (++__dalliance_saltSeed));\n}\n\nfunction textXHR(url, callback, opts) {\n if (opts && opts.salt)\n url = saltURL(url);\n\n try {\n var timeout;\n if (opts && opts.timeout) {\n timeout = setTimeout(\n function () {\n console.log('timing out ' + url);\n req.abort();\n return callback(null, 'Timeout');\n },\n opts.timeout\n );\n }\n\n var req = new XMLHttpRequest();\n req.onreadystatechange = function () {\n if (req.readyState == 4) {\n if (timeout)\n clearTimeout(timeout);\n if (req.status < 200 || req.status >= 300) {\n callback(null, 'Error code ' + req.status);\n } else {\n callback(req.responseText);\n }\n }\n };\n\n req.open('GET', url, true);\n req.responseType = 'text';\n\n if (opts && opts.credentials) {\n req.withCredentials = true;\n }\n req.send();\n } catch (e) {\n callback(null, 'Exception ' + e);\n }\n}\n\nfunction relativeURL(base, rel) {\n // FIXME quite naive -- good enough for trackhubs?\n\n if (rel.indexOf('http:') == 0 || rel.indexOf('https:') == 0) {\n return rel;\n }\n\n var li = base.lastIndexOf('/');\n if (li >= 0) {\n return base.substr(0, li + 1) + rel;\n } else {\n return rel;\n }\n}\n\nvar AMINO_ACID_TRANSLATION = {\n 'TTT': 'F',\n 'TTC': 'F',\n 'TTA': 'L',\n 'TTG': 'L',\n 'CTT': 'L',\n 'CTC': 'L',\n 'CTA': 'L',\n 'CTG': 'L',\n 'ATT': 'I',\n 'ATC': 'I',\n 'ATA': 'I',\n 'ATG': 'M',\n 'GTT': 'V',\n 'GTC': 'V',\n 'GTA': 'V',\n 'GTG': 'V',\n 'TCT': 'S',\n 'TCC': 'S',\n 'TCA': 'S',\n 'TCG': 'S',\n 'CCT': 'P',\n 'CCC': 'P',\n 'CCA': 'P',\n 'CCG': 'P',\n 'ACT': 'T',\n 'ACC': 'T',\n 'ACA': 'T',\n 'ACG': 'T',\n 'GCT': 'A',\n 'GCC': 'A',\n 'GCA': 'A',\n 'GCG': 'A',\n 'TAT': 'Y',\n 'TAC': 'Y',\n 'TAA': '*', // stop\n 'TAG': '*', // stop\n 'CAT': 'H',\n 'CAC': 'H',\n 'CAA': 'Q',\n 'CAG': 'Q',\n 'AAT': 'N',\n 'AAC': 'N',\n 'AAA': 'K',\n 'AAG': 'K',\n 'GAT': 'D',\n 'GAC': 'D',\n 'GAA': 'E',\n 'GAG': 'E',\n 'TGT': 'C',\n 'TGC': 'C',\n 'TGA': '*', // stop\n 'TGG': 'W',\n 'CGT': 'R',\n 'CGC': 'R',\n 'CGA': 'R',\n 'CGG': 'R',\n 'AGT': 'S',\n 'AGC': 'S',\n 'AGA': 'R',\n 'AGG': 'R',\n 'GGT': 'G',\n 'GGC': 'G',\n 'GGA': 'G',\n 'GGG': 'G'\n};\n\nfunction resolveUrlToPage(rel) {\n return makeElement('a', null, { href: rel }).href;\n}\n\n//\n// Missing APIs\n//\n\nif (!('trim' in String.prototype)) {\n String.prototype.trim = function () {\n return this.replace(/^\\s+/, '').replace(/\\s+$/, '');\n };\n}\n\nexport {\n textXHR,\n relativeURL,\n resolveUrlToPage,\n shallowCopy,\n pusho,\n arrayIndexOf,\n arrayEquals,\n pick,\n makeElement,\n makeElementNS,\n removeChildren,\n miniJSONify,\n Observed,\n Awaited,\n AMINO_ACID_TRANSLATION\n};\n","/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */\n\n//\n// Dalliance Genome Explorer\n// (c) Thomas Down 2006-2011\n//\n// bin.js general binary data support\n//\n\n\nimport { b64_sha1 } from './sha1';\nimport { shallowCopy } from './utils';\n\nfunction BlobFetchable(b) {\n this.blob = b;\n}\n\nBlobFetchable.prototype.slice = function (start, length) {\n var b;\n\n if (this.blob.slice) {\n if (length) {\n b = this.blob.slice(start, start + length);\n } else {\n b = this.blob.slice(start);\n }\n } else {\n if (length) {\n b = this.blob.webkitSlice(start, start + length);\n } else {\n b = this.blob.webkitSlice(start);\n }\n }\n return new BlobFetchable(b);\n};\n\nBlobFetchable.prototype.salted = function () {\n return this;\n};\n\nif (typeof (FileReader) !== 'undefined') {\n // console.log('defining async BlobFetchable.fetch');\n\n BlobFetchable.prototype.fetch = function (callback) {\n var reader = new FileReader();\n reader.onloadend = function (ev) {\n callback(bstringToBuffer(reader.result));\n };\n reader.readAsBinaryString(this.blob);\n }\n\n} else {\n BlobFetchable.prototype.fetch = function (callback) {\n var reader = new FileReaderSync();\n try {\n var res = reader.readAsArrayBuffer(this.blob);\n callback(res);\n } catch (e) {\n callback(null, e);\n }\n }\n}\n\nfunction URLFetchable(url, start, end, opts) {\n if (!opts) {\n if (typeof start === 'object') {\n opts = start;\n start = undefined;\n } else {\n opts = {};\n }\n }\n\n this.url = url;\n this.start = start || 0;\n if (end) {\n this.end = end;\n }\n this.opts = opts;\n}\n\nURLFetchable.prototype.slice = function (s, l) {\n if (s < 0) {\n throw 'Bad slice ' + s;\n }\n\n var ns = this.start, ne = this.end;\n if (ns && s) {\n ns = ns + s;\n } else {\n ns = s || ns;\n }\n if (l && ns) {\n ne = ns + l - 1;\n } else {\n ne = ne || l - 1;\n }\n return new URLFetchable(this.url, ns, ne, this.opts);\n};\n\nvar seed = 0;\nvar isSafari = typeof (navigator) !== 'undefined' &&\n navigator.userAgent.indexOf('Safari') >= 0 &&\n navigator.userAgent.indexOf('Chrome') < 0;\n\nURLFetchable.prototype.fetchAsText = function (callback) {\n var thisB = this;\n\n this.getURL().then(function (url) {\n try {\n var req = new XMLHttpRequest();\n var length;\n if ((isSafari || thisB.opts.salt) && url.indexOf('?') < 0) {\n url = url + '?salt=' + b64_sha1('' + Date.now() + ',' + (++seed));\n }\n req.open('GET', url, true);\n\n if (thisB.end) {\n if (thisB.end - thisB.start > 100000000) {\n throw 'Monster fetch!';\n }\n req.setRequestHeader('Range', 'bytes=' + thisB.start + '-' + thisB.end);\n length = thisB.end - thisB.start + 1;\n }\n\n req.onreadystatechange = function () {\n if (req.readyState == 4) {\n if (req.status == 200 || req.status == 206) {\n return callback(req.responseText);\n } else {\n return callback(null);\n }\n }\n };\n if (thisB.opts.credentials) {\n req.withCredentials = true;\n }\n req.send();\n } catch (e) {\n return callback(null);\n }\n }).catch(function (err) {\n console.log(err);\n return callback(null, err);\n });\n};\n\nURLFetchable.prototype.salted = function () {\n var o = shallowCopy(this.opts);\n o.salt = true;\n return new URLFetchable(this.url, this.start, this.end, o);\n};\n\nURLFetchable.prototype.getURL = function () {\n if (this.opts.resolver) {\n return this.opts.resolver(this.url).then(function (urlOrObj) {\n if (typeof urlOrObj === 'string') {\n return urlOrObj;\n } else {\n return urlOrObj.url;\n }\n });\n } else {\n return Promise.resolve(this.url);\n }\n};\n\nURLFetchable.prototype.fetch = function (callback, opts) {\n var thisB = this;\n\n opts = opts || {};\n var attempt = opts.attempt || 1;\n var truncatedLength = opts.truncatedLength;\n if (attempt > 3) {\n return callback(null);\n }\n\n this.getURL().then(function (url) {\n try {\n var timeout;\n if (opts.timeout && !thisB.opts.credentials) {\n timeout = setTimeout(\n function () {\n console.log('timing out ' + url);\n req.abort();\n return callback(null, 'Timeout');\n },\n opts.timeout\n );\n }\n\n var req = new XMLHttpRequest();\n var length;\n if ((isSafari || thisB.opts.salt) && url.indexOf('?') < 0) {\n url = url + '?salt=' + b64_sha1('' + Date.now() + ',' + (++seed));\n }\n req.open('GET', url, true);\n req.overrideMimeType('text/plain; charset=x-user-defined');\n if (thisB.end) {\n if (thisB.end - thisB.start > 100000000) {\n throw 'Monster fetch!';\n }\n req.setRequestHeader('Range', 'bytes=' + thisB.start + '-' + thisB.end);\n length = thisB.end - thisB.start + 1;\n }\n req.responseType = 'arraybuffer';\n req.onreadystatechange = function () {\n if (req.readyState == 4) {\n if (timeout)\n clearTimeout(timeout);\n if (req.status == 200 || req.status == 206) {\n if (req.response) {\n var bl = req.response.byteLength;\n if (length && length != bl && (!truncatedLength || bl != truncatedLength)) {\n return thisB.fetch(callback, { attempt: attempt + 1, truncatedLength: bl });\n } else {\n return callback(req.response);\n }\n } else if (req.mozResponseArrayBuffer) {\n return callback(req.mozResponseArrayBuffer);\n } else {\n var r = req.responseText;\n if (length && length != r.length && (!truncatedLength || r.length != truncatedLength)) {\n return thisB.fetch(callback, { attempt: attempt + 1, truncatedLength: r.length });\n } else {\n return callback(bstringToBuffer(req.responseText));\n }\n }\n } else {\n return thisB.fetch(callback, { attempt: attempt + 1 });\n }\n }\n };\n if (thisB.opts.credentials) {\n req.withCredentials = true;\n }\n req.send();\n } catch (e) {\n return callback(null);\n }\n }).catch(function (err) {\n console.log(err);\n return callback(null, err);\n });\n}\n\nfunction bstringToBuffer(result) {\n if (!result) {\n return null;\n }\n\n var ba = new Uint8Array(result.length);\n for (var i = 0; i < ba.length; ++i) {\n ba[i] = result.charCodeAt(i);\n }\n return ba.buffer;\n}\n\n// Read from Uint8Array\n\nvar convertBuffer = new ArrayBuffer(8);\nvar ba = new Uint8Array(convertBuffer);\nvar fa = new Float32Array(convertBuffer);\n\nfunction readFloat(buf, offset) {\n ba[0] = buf[offset];\n ba[1] = buf[offset + 1];\n ba[2] = buf[offset + 2];\n ba[3] = buf[offset + 3];\n return fa[0];\n}\n\nfunction readInt64(ba, offset) {\n return (ba[offset + 7] << 24) | (ba[offset + 6] << 16) | (ba[offset + 5] << 8) | (ba[offset + 4]);\n}\n\nconst M1 = 256,\n M2 = M1 * 256,\n M3 = M2 * 256,\n M4 = M3 * 256,\n M5 = M4 * 256;\n\nfunction readInt64LE(ba, offset) {\n return (ba[offset]) + (ba[offset + 1] * M1) + (ba[offset + 2] * M2) + (ba[offset + 3] * M3) + (ba[offset + 4] * M4) + (ba[offset + 5] * M5);\n}\n\nfunction readInt64BE(ba, offset) {\n return (ba[offset + 7]) + (ba[offset + 6] * M1) + (ba[offset + 5] * M2) + (ba[offset + 4] * M3) + (ba[offset + 3] * M4) + (ba[offset + 2] * M5);\n}\n\nfunction readInt(ba, offset) {\n return (ba[offset + 3] << 24) | (ba[offset + 2] << 16) | (ba[offset + 1] << 8) | (ba[offset]);\n}\n\nfunction readShort(ba, offset) {\n return (ba[offset + 1] << 8) | (ba[offset]);\n}\n\nfunction readByte(ba, offset) {\n return ba[offset];\n}\n\nfunction readIntBE(ba, offset) {\n return (ba[offset] << 24) | (ba[offset + 1] << 16) | (ba[offset + 2] << 8) | (ba[offset + 3]);\n}\n\nexport {\n BlobFetchable,\n URLFetchable,\n readInt,\n readIntBE,\n readInt64,\n readInt64LE,\n readInt64BE,\n readShort,\n readByte,\n readFloat\n};\n","/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */\n\n//\n// Dalliance Genome Explorer\n// (c) Thomas Down 2006-2011\n//\n// lh3utils.js: common support for lh3's file formats\n//\n\nimport { arrayCopy, inflateBuffer as jszlib_inflate_buffer } from 'jszlib';\n\n\nfunction Vob(b, o) {\n this.block = b;\n this.offset = o;\n}\n\nVob.prototype.toString = function() {\n return '' + this.block + ':' + this.offset;\n};\n\nfunction readVob(ba, offset, allowZero) {\n var block = ((ba[offset+6] & 0xff) * 0x100000000) + ((ba[offset+5] & 0xff) * 0x1000000) + ((ba[offset+4] & 0xff) * 0x10000) + ((ba[offset+3] & 0xff) * 0x100) + ((ba[offset+2] & 0xff));\n var bint = (ba[offset+1] << 8) | (ba[offset]);\n if (block == 0 && bint == 0 && !allowZero) {\n return null; // Should only happen in the linear index?\n } else {\n return new Vob(block, bint);\n }\n}\n\nfunction unbgzf(data, lim) {\n lim = Math.min(lim || 1, data.byteLength - 50);\n var oBlockList = [];\n var ptr = [0];\n var totalSize = 0;\n\n while (ptr[0] < lim) {\n var ba = new Uint8Array(data, ptr[0], 12); // FIXME is this enough for all credible BGZF block headers?\n var xlen = (ba[11] << 8) | (ba[10]);\n // dlog('xlen[' + (ptr[0]) +']=' + xlen);\n var unc = jszlib_inflate_buffer(data, 12 + xlen + ptr[0], Math.min(65536, data.byteLength - 12 - xlen - ptr[0]), ptr);\n ptr[0] += 8;\n totalSize += unc.byteLength;\n oBlockList.push(unc);\n }\n\n if (oBlockList.length == 1) {\n return oBlockList[0];\n } else {\n var out = new Uint8Array(totalSize);\n var cursor = 0;\n for (var i = 0; i < oBlockList.length; ++i) {\n var b = new Uint8Array(oBlockList[i]);\n arrayCopy(b, 0, out, cursor, b.length);\n cursor += b.length;\n }\n return out.buffer;\n }\n}\n\nfunction Chunk(minv, maxv) {\n this.minv = minv; this.maxv = maxv;\n}\n\n\n//\n// Binning (transliterated from SAM1.3 spec)\n//\n\n/* calculate bin given an alignment covering [beg,end) (zero-based, half-close-half-open) */\nfunction reg2bin(beg, end)\n{\n --end;\n if (beg>>14 == end>>14) return ((1<<15)-1)/7 + (beg>>14);\n if (beg>>17 == end>>17) return ((1<<12)-1)/7 + (beg>>17);\n if (beg>>20 == end>>20) return ((1<<9)-1)/7 + (beg>>20);\n if (beg>>23 == end>>23) return ((1<<6)-1)/7 + (beg>>23);\n if (beg>>26 == end>>26) return ((1<<3)-1)/7 + (beg>>26);\n return 0;\n}\n\n/* calculate the list of bins that may overlap with region [beg,end) (zero-based) */\nvar MAX_BIN = (((1<<18)-1)/7);\nfunction reg2bins(beg, end)\n{\n var i = 0, k, list = [];\n --end;\n list.push(0);\n for (k = 1 + (beg>>26); k <= 1 + (end>>26); ++k) list.push(k);\n for (k = 9 + (beg>>23); k <= 9 + (end>>23); ++k) list.push(k);\n for (k = 73 + (beg>>20); k <= 73 + (end>>20); ++k) list.push(k);\n for (k = 585 + (beg>>17); k <= 585 + (end>>17); ++k) list.push(k);\n for (k = 4681 + (beg>>14); k <= 4681 + (end>>14); ++k) list.push(k);\n return list;\n}\n\nexport { unbgzf, readVob, reg2bin, reg2bins, Chunk };\n","/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */\n\n//\n// Dalliance Genome Explorer\n// (c) Thomas Down 2006-2010\n//\n// spans.js: JavaScript Intset/Location port.\n//\n\nfunction Range(min, max)\n{\n if (typeof(min) != 'number' || typeof(max) != 'number')\n throw 'Bad range ' + min + ',' + max;\n this._min = min;\n this._max = max;\n}\n\nRange.prototype.min = function() {\n return this._min;\n};\n\nRange.prototype.max = function() {\n return this._max;\n};\n\nRange.prototype.contains = function(pos) {\n return pos >= this._min && pos <= this._max;\n};\n\nRange.prototype.isContiguous = function() {\n return true;\n};\n\nRange.prototype.ranges = function() {\n return [this];\n};\n\nRange.prototype._pushRanges = function(ranges) {\n ranges.push(this);\n};\n\nRange.prototype.toString = function() {\n return '[' + this._min + '-' + this._max + ']';\n};\n\nfunction _Compound(ranges) {\n // given: a set of unsorted possibly overlapping ranges\n // sort the input ranges\n var sorted = ranges.sort(_rangeOrder);\n // merge overlaps between adjacent ranges\n var merged = [];\n var current = sorted.shift();\n sorted.forEach(function(range) {\n if (range._min <= current._max) {\n if (range._max > current._max) {\n current._max = range._max;\n }\n }\n else {\n merged.push(current);\n current = range;\n }\n });\n merged.push(current);\n this._ranges = merged;\n}\n\n_Compound.prototype.min = function() {\n return this._ranges[0].min();\n};\n\n_Compound.prototype.max = function() {\n return this._ranges[this._ranges.length - 1].max();\n};\n\n// returns the index of the first range that is not less than pos\n_Compound.prototype.lower_bound = function(pos) {\n // first check if pos is out of range\n var r = this.ranges();\n if (pos > this.max()) return r.length;\n if (pos < this.min()) return 0;\n // do a binary search\n var a=0, b=r.length - 1;\n while (a <= b) {\n var m = Math.floor((a+b)/2);\n if (pos > r[m]._max) {\n a = m+1;\n }\n else if (pos < r[m]._min) {\n b = m-1;\n }\n else {\n return m;\n }\n }\n return a;\n};\n\n_Compound.prototype.contains = function(pos) {\n var lb = this.lower_bound(pos);\n if (lb < this._ranges.length && this._ranges[lb].contains(pos)) {\n return true;\n }\n return false;\n};\n\n_Compound.prototype.insertRange = function(range) {\n var lb = this.lower_bound(range._min);\n if (lb === this._ranges.length) { // range follows this\n this._ranges.push(range);\n return;\n }\n\n var r = this.ranges();\n if (range._max < r[lb]._min) { // range preceeds lb\n this._ranges.splice(lb,0,range);\n return;\n }\n\n // range overlaps lb (at least)\n if (r[lb]._min < range._min) range._min = r[lb]._min;\n var ub = lb+1;\n while (ub < r.length && r[ub]._min <= range._max) {\n ub++;\n }\n ub--;\n // ub is the upper bound of the new range\n if (r[ub]._max > range._max) range._max = r[ub]._max;\n\n // splice range into this._ranges\n this._ranges.splice(lb,ub-lb+1,range);\n return;\n};\n\n_Compound.prototype.isContiguous = function() {\n return this._ranges.length > 1;\n};\n\n_Compound.prototype.ranges = function() {\n return this._ranges;\n};\n\n_Compound.prototype._pushRanges = function(ranges) {\n for (var ri = 0; ri < this._ranges.length; ++ri)\n ranges.push(this._ranges[ri]);\n};\n\n_Compound.prototype.toString = function() {\n var s = '';\n for (var r = 0; r < this._ranges.length; ++r) {\n if (r>0) {\n s = s + ',';\n }\n s = s + this._ranges[r].toString();\n }\n return s;\n};\n\nfunction union(s0, s1) {\n if (! (s0 instanceof _Compound)) {\n if (! (s0 instanceof Array))\n s0 = [s0];\n s0 = new _Compound(s0);\n }\n\n if (s1)\n s0.insertRange(s1);\n\n return s0;\n}\n\nfunction intersection(s0, s1) {\n var r0 = s0.ranges();\n var r1 = s1.ranges();\n var l0 = r0.length, l1 = r1.length;\n var i0 = 0, i1 = 0;\n var or = [];\n\n while (i0 < l0 && i1 < l1) {\n var s0 = r0[i0], s1 = r1[i1];\n var lapMin = Math.max(s0.min(), s1.min());\n var lapMax = Math.min(s0.max(), s1.max());\n if (lapMax >= lapMin) {\n or.push(new Range(lapMin, lapMax));\n }\n if (s0.max() > s1.max()) {\n ++i1;\n } else {\n ++i0;\n }\n }\n\n if (or.length == 0) {\n return null; // FIXME\n } else if (or.length == 1) {\n return or[0];\n } else {\n return new _Compound(or);\n }\n}\n\nfunction coverage(s) {\n var tot = 0;\n var rl = s.ranges();\n for (var ri = 0; ri < rl.length; ++ri) {\n var r = rl[ri];\n tot += (r.max() - r.min() + 1);\n }\n return tot;\n}\n\n\n\nfunction rangeOrder(a, b)\n{\n if (a.min() < b.min()) {\n return -1;\n } else if (a.min() > b.min()) {\n return 1;\n } else if (a.max() < b.max()) {\n return -1;\n } else if (b.max() > a.max()) {\n return 1;\n } else {\n return 0;\n }\n}\n\nfunction _rangeOrder(a, b)\n{\n if (a._min < b._min) {\n return -1;\n } else if (a._min > b._min) {\n return 1;\n } else if (a._max < b._max) {\n return -1;\n } else if (b._max > a._max) {\n return 1;\n } else {\n return 0;\n }\n}\n\n\nexport {\n Range,\n union,\n intersection,\n coverage,\n rangeOrder,\n _rangeOrder\n};\n","/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */\n\n//\n// Dalliance Genome Explorer\n// (c) Thomas Down 2006-2011\n//\n// tabix.js: basic support for tabix-indexed flatfiles\n//\n\n\nimport { readInt, readShort, readByte, readInt64, readFloat } from './bin';\nimport { readVob, unbgzf,reg2bins, Chunk } from './lh3utils';\nimport { intersection, Range, union } from './spans';\n\n\nvar TABIX_MAGIC = 0x01494254;\n\nfunction TabixFile() {}\n\nfunction connectTabix(data, tbi, callback) {\n var tabix = new TabixFile();\n tabix.data = data;\n tabix.tbi = tbi;\n\n tabix.tbi.fetch(function(header) { // Do we really need to fetch the whole thing? :-(\n if (!header) {\n return callback(null, \"Couldn't access Tabix\");\n }\n\n var unchead = unbgzf(header, header.byteLength);\n var uncba = new Uint8Array(unchead);\n var magic = readInt(uncba, 0);\n if (magic != TABIX_MAGIC) {\n return callback(null, 'Not a tabix index');\n }\n\n var nref = readInt(uncba, 4);\n tabix.format = readInt(uncba, 8);\n tabix.colSeq = readInt(uncba, 12);\n tabix.colStart = readInt(uncba, 16);\n tabix.colEnd = readInt(uncba, 20);\n tabix.meta = readInt(uncba, 24);\n tabix.skip = readInt(uncba, 28);\n var nameLength = readInt(uncba, 32);\n\n tabix.indices = [];\n\n var p = 36;\n tabix.chrToIndex = {};\n tabix.indexToChr = [];\n for (var i = 0; i < nref; ++i) {\n var name = ''\n\n while (true) {\n var ch = uncba[p++];\n if (ch == 0)\n break;\n\n name += String.fromCharCode(ch);\n }\n\n tabix.chrToIndex[name] = i;\n if (name.indexOf('chr') == 0) {\n tabix.chrToIndex[name.substring(3)] = i;\n } else {\n tabix.chrToIndex['chr' + name] = i;\n }\n tabix.indexToChr.push(name);\n }\n\n var minBlockIndex = 1000000000;\n for (var ref = 0; ref < nref; ++ref) {\n var blockStart = p;\n var nbin = readInt(uncba, p); p += 4;\n for (var b = 0; b < nbin; ++b) {\n var bin = readInt(uncba, p);\n var nchnk = readInt(uncba, p+4);\n p += 8 + (nchnk * 16);\n }\n var nintv = readInt(uncba, p); p += 4;\n\n var q = p;\n for (var i = 0; i < nintv; ++i) {\n var v = readVob(uncba, q); q += 8;\n if (v) {\n var bi = v.block;\n if (v.offset > 0)\n bi += 65536;\n\n if (bi < minBlockIndex)\n minBlockIndex = bi;\n break;\n }\n }\n p += (nintv * 8);\n\n\n var ub = uncba;\n if (nbin > 0) {\n tabix.indices[ref] = new Uint8Array(unchead, blockStart, p - blockStart);\n }\n }\n\n tabix.headerMax = minBlockIndex;\n\n callback(tabix);\n }, {timeout: 50000});\n}\n\n// Copy-paste from BamFile\n\nTabixFile.prototype.blocksForRange = function(refId, min, max) {\n var index = this.indices[refId];\n if (!index) {\n return [];\n }\n\n var intBinsL = reg2bins(min, max);\n var intBins = [];\n for (var i = 0; i < intBinsL.length; ++i) {\n intBins[intBinsL[i]] = true;\n }\n var leafChunks = [], otherChunks = [];\n\n var nbin = readInt(index, 0);\n var p = 4;\n for (var b = 0; b < nbin; ++b) {\n var bin = readInt(index, p);\n var nchnk = readInt(index, p+4);\n p += 8;\n if (intBins[bin]) {\n for (var c = 0; c < nchnk; ++c) {\n var cs = readVob(index, p, true);\n var ce = readVob(index, p + 8, true);\n (bin < 4681 ? otherChunks : leafChunks).push(new Chunk(cs, ce));\n p += 16;\n }\n } else {\n p += (nchnk * 16);\n }\n }\n\n var nintv = readInt(index, p);\n var lowest = null;\n var minLin = Math.min(min>>14, nintv - 1), maxLin = Math.min(max>>14, nintv - 1);\n for (var i = minLin; i <= maxLin; ++i) {\n var lb = readVob(index, p + 4 + (i * 8));\n if (!lb) {\n continue;\n }\n if (!lowest || lb.block < lowest.block || lb.offset < lowest.offset) {\n lowest = lb;\n }\n }\n\n var prunedOtherChunks = [];\n if (lowest != null) {\n for (var i = 0; i < otherChunks.length; ++i) {\n var chnk = otherChunks[i];\n if (chnk.maxv.block >= lowest.block && chnk.maxv.offset >= lowest.offset) {\n prunedOtherChunks.push(chnk);\n }\n }\n }\n otherChunks = prunedOtherChunks;\n\n var intChunks = [];\n for (var i = 0; i < otherChunks.length; ++i) {\n intChunks.push(otherChunks[i]);\n }\n for (var i = 0; i < leafChunks.length; ++i) {\n intChunks.push(leafChunks[i]);\n }\n\n intChunks.sort(function(c0, c1) {\n var dif = c0.minv.block - c1.minv.block;\n if (dif != 0) {\n return dif;\n } else {\n return c0.minv.offset - c1.minv.offset;\n }\n });\n var mergedChunks = [];\n if (intChunks.length > 0) {\n var cur = intChunks[0];\n for (var i = 1; i < intChunks.length; ++i) {\n var nc = intChunks[i];\n if (nc.minv.block == cur.maxv.block /* && nc.minv.offset == cur.maxv.offset */) { // no point splitting mid-block\n cur = new Chunk(cur.minv, nc.maxv);\n } else {\n mergedChunks.push(cur);\n cur = nc;\n }\n }\n mergedChunks.push(cur);\n }\n\n return mergedChunks;\n}\n\nTabixFile.prototype.fetch = function(chr, min, max, callback) {\n var thisB = this;\n\n var chrId = this.chrToIndex[chr];\n if (chrId == undefined)\n return callback([]);\n\n var canonicalChr = this.indexToChr[chrId];\n\n var chunks;\n if (chrId === undefined) {\n chunks = [];\n } else {\n chunks = this.blocksForRange(chrId, min, max);\n if (!chunks) {\n callback(null, 'Error in index fetch');\n }\n }\n\n var records = [];\n var index = 0;\n var data;\n\n function tramp() {\n if (index >= chunks.length) {\n return callback(records);\n } else if (!data) {\n var c = chunks[index];\n var fetchMin = c.minv.block;\n var fetchMax = c.maxv.block + (1<<16); // *sigh*\n thisB.data.slice(fetchMin, fetchMax - fetchMin).fetch(function(r) { //2^16 fetch, 65536 bytes\n try {\n data = unbgzf(r, c.maxv.block - c.minv.block + 1);\n return tramp();\n } catch (e) {\n return callback(null, e);\n }\n });\n } else {\n var ba = new Uint8Array(data);\n thisB.readRecords(ba, chunks[index].minv.offset, records, min, max, canonicalChr);\n data = null;\n ++index;\n return tramp();\n }\n }\n\n try {\n tramp();\n } catch (e) {\n callback(null, e);\n }\n}\n\nTabixFile.prototype.readRecords = function(ba, offset, sink, min, max, chr) {\n LINE_LOOP:\n while (true) {\n var line = '';\n while (offset < ba.length) {\n var ch = ba[offset++];\n if (ch == 10) {\n var toks = line.split('\\t');\n\n if (toks[this.colSeq - 1] == chr) {\n var fmin = parseInt(toks[this.colStart - 1]);\n var fmax = fmin;\n if (this.colEnd > 0)\n fmax = parseInt(toks[this.colEnd - 1]);\n if (this.format & 0x10000) ++fmin;\n\n if (fmin <= max && fmax >= min)\n sink.push(line);\n }\n continue LINE_LOOP;\n } else {\n line += String.fromCharCode(ch);\n }\n }\n return;\n }\n}\n\nTabixFile.prototype.fetchHeader = function(callback, opts) {\n // This routine fetches a large block of data from the start of the file, and then selects \"headers\" based\n // on one of three possible criteria:\n // - metaOnly (tabix -H compatible): only lines that begin with the meta character are accepted\n // - skipped (get all \"skipped\" rows, based on tabix -S parameter when indexing)\n // - nLines (peek at the first x lines, which may return data as well as headers)\n\n // In all cases, the callback will return as soon as we reach the first line that does not meet these conditions.\n var defaults = { metaOnly: true, skipped: false, nLines: 0 };\n opts = opts || defaults;\n Object.keys(defaults).forEach(function(key) {\n if (!opts.hasOwnProperty(key)) {\n opts[key] = defaults[key];\n }\n });\n\n var self = this;\n var fetchPtr = 0, ptr = 0, line='';\n var lines = [];\n\n self.data.slice(0, self.headerMax).fetch(function(chnk) {\n if (!chnk) {\n return callback(null, \"Fetch failed\");\n }\n var ba = new Uint8Array(unbgzf(chnk, chnk.byteLength));\n var ptr = 0, line = '', lines = [];\n while (ptr < ba.length) {\n var ch = ba[ptr++];\n if (ch == 10) {\n if ((opts.metaOnly && line.charCodeAt(0) == self.meta) ||\n (opts.skipped && lines.length < self.skip) ||\n (opts.nLines && lines.length < opts.nLines))\n {\n lines.push(line);\n line = '';\n } else {\n return callback(lines);\n }\n } else {\n line += String.fromCharCode(ch);\n }\n }\n callback(lines);\n });\n};\n\nexport { connectTabix, TABIX_MAGIC };\n","import { connectTabix } from './lib/tabix';\nimport { URLFetchable, BlobFetchable } from './lib/bin';\n\n\n// Wrap the reader in a Promise API for LZ compatibility\nfunction urlReader(dataUrl, indexUrl) {\n return new Promise(function(resolve, reject) {\n connectTabix(new URLFetchable(dataUrl), new URLFetchable(indexUrl),\n function (reader, err) {\n if (err) {\n reject(err);\n } else {\n resolve(reader);\n }\n });\n });\n}\n\nfunction blobReader(data_file, tabix_file) {\n return new Promise(function(resolve, reject) {\n connectTabix(new BlobFetchable(data_file), new BlobFetchable(tabix_file),\n function (reader, err) {\n if (err) {\n reject(err);\n } else {\n resolve(reader);\n }\n });\n });\n}\n\nexport { urlReader, blobReader };\n","/**\n * An adapter that fetches data from a remote Tabix-indexed datafile, instead of a RESTful API.\n * Requires a generic user-specified parser function.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~TabixUrlSource}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - Vendor assets\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import LzTabixSource from 'locuszoom/esm/ext/lz-tabix-source';\n * LocusZoom.use(LzTabixSource);\n * ```\n *\n * Then use the Adapter made available by this extension. For example:\n *\n * ```javascript\n * data_sources.add(\"assoc\", [\"TabixUrlSource\", {\n * url_data: 'https://s3-bucket.example/tabix-indexed-bgzip-file.gz',\n * parser_func: (line_of_text) => object_of_parsed_data_for_this_line,\n * // Tabix performs region queries. If you are fetching interval data (one end outside the edge of the plot), then\n * // \"overfetching\" can help to ensure that data partially outside the view region is retrieved\n * // If you are fetching single-point data like association summary stats, then overfetching is unnecessary\n * overfetch: 0.25\n * }]);\n * ```\n *\n * @module\n */\nimport { urlReader } from 'tabix-reader';\n\n\nfunction install(LocusZoom) {\n const BaseLZAdapter = LocusZoom.Adapters.get('BaseLZAdapter');\n\n /**\n * Loads data from a remote Tabix file (if the file host has been configured with proper\n * CORS and Range header support). For instructions on how to configure a remote file host such as S3 or\n * Google Cloud storage to serve files in the manner required, see:\n * https://docs.cancergenomicscloud.org/docs/enabling-cross-origin-resource-sharing-cors#CORS\n *\n * @alias module:LocusZoom_Adapters~TabixUrlSource\n * @see {@link module:ext/lz-tabix-source} for required extension and installation instructions\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {function} config.parser_func A function that parses a single line of text and returns (usually) a\n * structured object of data fields\n * @param {string} config.url_data The URL for the bgzipped and tabix-indexed file\n * @param {string} [config.url_tbi] The URL for the tabix index. Defaults to `url_data` + '.tbi'\n * @param {Promise} [config.reader] The URL for tabix-reader instance that provides the data. Mutually exclusive with providing a URL.\n * Most LocusZoom usages will not pass an external reader. This option exists for websites like LocalZoom that accept\n * many file formats and want to perform input validation before creating the plot: \"select parser options\", etc.\n * @param {number} [config.overfetch = 0] Optionally fetch more data than is required to satisfy the\n * region query. (specified as a fraction of the region size, 0-1).\n * Useful for sources where interesting features might lie near the edges of the plot, eg BED track intervals.\n */\n class TabixUrlSource extends BaseLZAdapter {\n constructor(config) {\n super(config);\n if (!config.parser_func || !(config.url_data || config.reader)) {\n throw new Error('Tabix source is missing required configuration options');\n }\n this.parser = config.parser_func;\n this.url_data = config.url_data;\n this.url_tbi = config.url_tbi || `${this.url_data}.tbi`;\n\n // In tabix mode, sometimes we want to fetch a slightly larger region than is displayed, in case a\n // feature is on the edge of what the tabix query would return.\n // Specify overfetch in units of % of total region size. (\"fetch 10% extra before and after\")\n this._overfetch = config.overfetch || 0;\n\n if (this._overfetch < 0 || this._overfetch > 1) {\n throw new Error('Overfetch must be specified as a fraction (0-1) of the requested region size');\n }\n\n // Assuming that the `tabix-reader` library has been loaded via a CDN, this will create the reader\n // Since fetching the index is a remote operation, all reader usages will be via an async interface.\n if (this.url_data) {\n this._reader_promise = urlReader(this.url_data, this.url_tbi).catch(function () {\n throw new Error('Failed to create a tabix reader from the provided URL');\n });\n } else {\n this._reader_promise = Promise.resolve(config.reader);\n }\n }\n\n _performRequest(options) {\n return new Promise((resolve, reject) => {\n // Ensure that the reader is fully created (and index available), then make a query\n const region_start = options.start;\n const region_end = options.end;\n const extra_amount = this._overfetch * (region_end - region_start);\n\n const start = options.start - extra_amount;\n const end = options.end + extra_amount;\n this._reader_promise.then((reader) => {\n reader.fetch(options.chr, start, end, function (data, err) {\n if (err) {\n reject(new Error('Could not read requested region. This may indicate an error with the .tbi index.'));\n }\n resolve(data);\n });\n });\n });\n }\n\n _normalizeResponse(records) {\n // Parse the data from lines of text to objects\n return records.map(this.parser);\n }\n }\n\n LocusZoom.Adapters.add('TabixUrlSource', TabixUrlSource);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-widget-addons.min.js b/dist/ext/lz-widget-addons.min.js index c5b86e27..0e0ff5d9 100644 --- a/dist/ext/lz-widget-addons.min.js +++ b/dist/ext/lz-widget-addons.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.4 */ -var LzWidgetAddons;(()=>{"use strict";var t={d:(e,o)=>{for(var a in o)t.o(o,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:o[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>i});d3;Math.sqrt(3);function o(t){return JSON.parse(JSON.stringify(t))}const a=["highlight","select","fade","hide"],l=["highlighted","selected","faded","hidden"],s=["unhighlight","deselect","unfade","show"];function n(t){const e=t.Widgets.get("_Button"),n=t.Widgets.get("BaseWidget");const i=function(){const e=t.Layouts.get("tooltip","standard_association",{unnamespaced:!0});return e.html+='Condition on Variant
',e}(),r=function(){const e=t.Layouts.get("toolbar","standard_association",{unnamespaced:!0});return e.widgets.push({type:"covariates_model",button_html:"Model",button_title:"Show and edit covariates currently in model",position:"left"}),e}();t.Widgets.add("covariates_model",class extends n{initialize(){this.parent_plot.state.model=this.parent_plot.state.model||{},this.parent_plot.state.model.covariates=this.parent_plot.state.model.covariates||[],this.parent_plot.CovariatesModel={button:this,add:t=>{const e=this.parent_plot,a=o(t);"object"==typeof t&&"string"!=typeof a.html&&(a.html="function"==typeof t.toHTML?t.toHTML():t.toString());for(let t=0;t{const e=this.parent_plot;if(void 0===e.state.model.covariates[t])throw new Error(`Unable to remove model covariate, invalid index: ${t.toString()}`);return e.state.model.covariates.splice(t,1),e.applyState(),e.CovariatesModel.updateWidget(),e},removeAll:()=>{const t=this.parent_plot;return t.state.model.covariates=[],t.applyState(),t.CovariatesModel.updateWidget(),t},updateWidget:()=>{this.button.update(),this.button.menu.update()}}}update(){return this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=this.button.menu.inner_selector;if(t.html(""),void 0!==this.parent_plot.state.model.html&&t.append("div").html(this.parent_plot.state.model.html),this.parent_plot.state.model.covariates.length){t.append("h5").html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);const e=t.append("table");this.parent_plot.state.model.covariates.forEach(((t,o)=>{const a="object"==typeof t&&"string"==typeof t.html?t.html:t.toString(),l=e.append("tr");l.append("td").append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","0em").on("click",(()=>this.parent_plot.CovariatesModel.removeByIdx(o))).html("×"),l.append("td").html(a)})),t.append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","4px").html("× Remove All Covariates").on("click",(()=>this.parent_plot.CovariatesModel.removeAll()))}else t.append("i").html("no covariates in model")})),this.button.preUpdate=()=>{let t="Model";const e=this.parent_plot.state.model.covariates.length;if(e){t+=` (${e} ${e>1?"covariates":"covariate"})`}this.button.setHtml(t).disable(!1)},this.button.show()),this}}),t.Widgets.add("data_layers",class extends n{update(){return"string"!=typeof this.layout.button_html&&(this.layout.button_html="Data Layers"),"string"!=typeof this.layout.button_title&&(this.layout.button_title="Manipulate Data Layers (sort, dim, show/hide, etc.)"),this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html("");const t=this.button.menu.inner_selector.append("table");return this.parent_panel.data_layer_ids_by_z_index.slice().reverse().forEach(((e,o)=>{const n=this.parent_panel.data_layers[e],i="string"!=typeof n.layout.name?n.id:n.layout.name,r=t.append("tr");r.append("td").html(i),this.layout.statuses.forEach((t=>{const e=l.indexOf(t),o=a[e];let i,d,u;n.global_statuses[t]?(i=s[e],d=`un${o}AllElements`,u="-highlighted"):(i=a[e],d=`${o}AllElements`,u=""),r.append("td").append("a").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}${u}`).style("margin-left","0em").on("click",(()=>{n[d](),this.button.menu.populate()})).html(i)}));const d=0===o,u=o===this.parent_panel.data_layer_ids_by_z_index.length-1,p=r.append("td");p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${u?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveBack(),this.button.menu.populate()})).html("▾").attr("title","Move layer down (further back)"),p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${d?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveForward(),this.button.menu.populate()})).html("▴").attr("title","Move layer up (further front)"),p.append("a").attr("class","lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red").style("margin-left","0em").on("click",(()=>(confirm(`Are you sure you want to remove the ${i} layer? This cannot be undone.`)&&n.parent.removeDataLayer(e),this.button.menu.populate()))).html("×").attr("title","Remove layer")})),this})),this.button.show()),this}}),t.Layouts.add("tooltip","covariates_model_association",i),t.Layouts.add("toolbar","covariates_model_plot",r)}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const i=n;LzWidgetAddons=e.default})(); +/*! Locuszoom 0.14.0-beta.1 */ +var LzWidgetAddons;(()=>{"use strict";var t={d:(e,o)=>{for(var a in o)t.o(o,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:o[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>i});d3;Math.sqrt(3);function o(t){return JSON.parse(JSON.stringify(t))}const a=["highlight","select","fade","hide"],l=["highlighted","selected","faded","hidden"],s=["unhighlight","deselect","unfade","show"];function n(t){const e=t.Widgets.get("_Button"),n=t.Widgets.get("BaseWidget");const i=function(){const e=t.Layouts.get("tooltip","standard_association");return e.html+='Condition on Variant
',e}(),r=function(){const e=t.Layouts.get("toolbar","standard_association");return e.widgets.push({type:"covariates_model",button_html:"Model",button_title:"Show and edit covariates currently in model",position:"left"}),e}();t.Widgets.add("covariates_model",class extends n{initialize(){this.parent_plot.state.model=this.parent_plot.state.model||{},this.parent_plot.state.model.covariates=this.parent_plot.state.model.covariates||[],this.parent_plot.CovariatesModel={button:this,add:t=>{const e=this.parent_plot,a=o(t);"object"==typeof t&&"string"!=typeof a.html&&(a.html="function"==typeof t.toHTML?t.toHTML():t.toString());for(let t=0;t{const e=this.parent_plot;if(void 0===e.state.model.covariates[t])throw new Error(`Unable to remove model covariate, invalid index: ${t.toString()}`);return e.state.model.covariates.splice(t,1),e.applyState(),e.CovariatesModel.updateWidget(),e},removeAll:()=>{const t=this.parent_plot;return t.state.model.covariates=[],t.applyState(),t.CovariatesModel.updateWidget(),t},updateWidget:()=>{this.button.update(),this.button.menu.update()}}}update(){return this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=this.button.menu.inner_selector;if(t.html(""),void 0!==this.parent_plot.state.model.html&&t.append("div").html(this.parent_plot.state.model.html),this.parent_plot.state.model.covariates.length){t.append("h5").html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);const e=t.append("table");this.parent_plot.state.model.covariates.forEach(((t,o)=>{const a="object"==typeof t&&"string"==typeof t.html?t.html:t.toString(),l=e.append("tr");l.append("td").append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","0em").on("click",(()=>this.parent_plot.CovariatesModel.removeByIdx(o))).html("×"),l.append("td").html(a)})),t.append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","4px").html("× Remove All Covariates").on("click",(()=>this.parent_plot.CovariatesModel.removeAll()))}else t.append("i").html("no covariates in model")})),this.button.preUpdate=()=>{let t="Model";const e=this.parent_plot.state.model.covariates.length;if(e){t+=` (${e} ${e>1?"covariates":"covariate"})`}this.button.setHtml(t).disable(!1)},this.button.show()),this}}),t.Widgets.add("data_layers",class extends n{update(){return"string"!=typeof this.layout.button_html&&(this.layout.button_html="Data Layers"),"string"!=typeof this.layout.button_title&&(this.layout.button_title="Manipulate Data Layers (sort, dim, show/hide, etc.)"),this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html("");const t=this.button.menu.inner_selector.append("table");return this.parent_panel._data_layer_ids_by_z_index.slice().reverse().forEach(((e,o)=>{const n=this.parent_panel.data_layers[e],i="string"!=typeof n.layout.name?n.id:n.layout.name,r=t.append("tr");r.append("td").html(i),this.layout.statuses.forEach((t=>{const e=l.indexOf(t),o=a[e];let i,d,u;n._global_statuses[t]?(i=s[e],d=`un${o}AllElements`,u="-highlighted"):(i=a[e],d=`${o}AllElements`,u=""),r.append("td").append("a").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}${u}`).style("margin-left","0em").on("click",(()=>{n[d](),this.button.menu.populate()})).html(i)}));const d=0===o,u=o===this.parent_panel._data_layer_ids_by_z_index.length-1,p=r.append("td");p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${u?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveBack(),this.button.menu.populate()})).html("▾").attr("title","Move layer down (further back)"),p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${d?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveForward(),this.button.menu.populate()})).html("▴").attr("title","Move layer up (further front)"),p.append("a").attr("class","lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red").style("margin-left","0em").on("click",(()=>(confirm(`Are you sure you want to remove the ${i} layer? This cannot be undone.`)&&n.parent.removeDataLayer(e),this.button.menu.populate()))).html("×").attr("title","Remove layer")})),this})),this.button.show()),this}}),t.Layouts.add("tooltip","covariates_model_association",i),t.Layouts.add("toolbar","covariates_model_plot",r)}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const i=n;LzWidgetAddons=e.default})(); //# sourceMappingURL=lz-widget-addons.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-widget-addons.min.js.map b/dist/ext/lz-widget-addons.min.js.map index 1e7b9d05..0e007b39 100644 --- a/dist/ext/lz-widget-addons.min.js.map +++ b/dist/ext/lz-widget-addons.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/ext/lz-widget-addons.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","Math","sqrt","deepCopy","item","JSON","parse","stringify","STATUS_VERBS","STATUS_ADJECTIVES","STATUS_ANTIVERBS","install","LocusZoom","_Button","Widgets","_BaseWidget","covariates_model_tooltip","covariates_model_association","Layouts","unnamespaced","html","covariates_model_plot","covariates_model_plot_toolbar","widgets","push","type","button_html","button_title","position","add","this","parent_plot","state","model","covariates","CovariatesModel","button","element_reference","plot","element","toHTML","toString","i","length","applyState","updateWidget","removeByIdx","idx","Error","splice","removeAll","update","menu","setColor","layout","color","setHtml","setTitle","setOnclick","populate","setPopulate","selector","inner_selector","append","table","forEach","covariate","row","attr","style","on","preUpdate","count","disable","show","parent_panel","data_layer_ids_by_z_index","slice","reverse","id","data_layer","data_layers","name","statuses","status_adj","status_idx","indexOf","status_verb","onclick","highlight","global_statuses","at_top","at_bottom","td","moveBack","moveForward","confirm","parent","removeDataLayer","use"],"mappings":"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","file":"ext/lz-widget-addons.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply namespaces to layout, recursively\n * @private\n */\nfunction applyNamespaces(element, namespace, default_namespace) {\n if (namespace) {\n if (typeof namespace == 'string') {\n namespace = { default: namespace };\n }\n } else {\n namespace = { default: '' };\n }\n if (typeof element == 'string') {\n const re = /\\{\\{namespace(\\[[A-Za-z_0-9]+\\]|)\\}\\}/g;\n let match, base, key, resolved_namespace;\n const replace = [];\n while ((match = re.exec(element)) !== null) {\n base = match[0];\n key = match[1].length ? match[1].replace(/(\\[|\\])/g, '') : null;\n resolved_namespace = default_namespace;\n if (namespace != null && typeof namespace == 'object' && typeof namespace[key] != 'undefined') {\n resolved_namespace = namespace[key] + (namespace[key].length ? ':' : '');\n }\n replace.push({ base: base, namespace: resolved_namespace });\n }\n for (let r in replace) {\n element = element.replace(replace[r].base, replace[r].namespace);\n }\n } else if (typeof element == 'object' && element != null) {\n if (typeof element.namespace != 'undefined') {\n const merge_namespace = (typeof element.namespace == 'string') ? { default: element.namespace } : element.namespace;\n namespace = merge(namespace, merge_namespace);\n }\n let namespaced_element, namespaced_property;\n for (let property in element) {\n if (property === 'namespace') {\n continue;\n }\n namespaced_element = applyNamespaces(element[property], namespace, default_namespace);\n namespaced_property = applyNamespaces(property, namespace, default_namespace);\n if (property !== namespaced_property) {\n delete element[property];\n }\n element[namespaced_property] = namespaced_element;\n }\n }\n return element;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {}\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, renameField };\n","/**\n * Optional LocusZoom extension: must be included separately, and after LocusZoom has been loaded\n *\n * This contains (reusable) code to power some (rarely used) demo features:\n * - The \"covariates model\" demo, in which an LZ toolbar widget is populated\n * with information by selecting points on the plot (see \"covariates model\" demo)\n * - The \"data layers\" button, which allows fine control over multiple data layers shown in the same panel\n * (show/hide, fade, change order, etc). This is powerful, but rarely used because showing many datasets in a small\n * space makes data hard to see. (see \"multiple phenotypes layered\" demo)\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n *\n * ```javascript\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```javascript\n * import LocusZoom from 'locuszoom';\n * import WidgetAddons from 'locuszoom/esm/ext/lz-widget-addons';\n * LocusZoom.use(WidgetAddons);\n * ```\n *\n * Then use the features made available by this extension. (see demos and documentation for guidance)\n *\n * @module\n */\nimport {deepCopy} from '../helpers/layouts';\n\n// In order to work in a UMD context, this module imports the top-level LocusZoom symbol\n\nconst STATUS_VERBS = ['highlight', 'select', 'fade', 'hide'];\nconst STATUS_ADJECTIVES = ['highlighted', 'selected', 'faded', 'hidden'];\nconst STATUS_ANTIVERBS = ['unhighlight', 'deselect', 'unfade', 'show'];\n\n\n// LocusZoom plugins work by exporting a function that receives the `LocusZoom` object\n// This allows them to work in many contexts (including script tags and ES6 imports)\nfunction install(LocusZoom) {\n const _Button = LocusZoom.Widgets.get('_Button');\n const _BaseWidget = LocusZoom.Widgets.get('BaseWidget');\n\n\n /**\n * Special button/menu to allow model building by tracking individual covariants. Will track a list of covariate\n * objects and store them in the special `model.covariates` field of plot `state`.\n *\n * This is a prototype widget for building a conditional analysis model, but it performs no calculation\n * functionality beyond building a list of items.\n * @alias module:ext/lz-widget-addons~covariates_model\n * @see module:LocusZoom_Widgets~BaseWidget\n * @param {object} layout\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n */\n class CovariatesModel extends _BaseWidget {\n initialize() {\n // Initialize state.model.covariates\n this.parent_plot.state.model = this.parent_plot.state.model || {};\n this.parent_plot.state.model.covariates = this.parent_plot.state.model.covariates || [];\n // Create an object at the plot level for easy access to interface methods in custom client-side JS\n /**\n * When a covariates model toolbar element is present, create (one) object at the plot level that exposes\n * widget data and state for custom interactions with other plot elements.\n */\n this.parent_plot.CovariatesModel = {\n /** @member {Button} */\n button: this,\n /**\n * Add an element to the model and show a representation of it in the toolbar widget menu. If the\n * element is already part of the model, do nothing (to avoid adding duplicates).\n * When plot state is changed, this will automatically trigger requests for new data accordingly.\n * @param {string|object} element_reference Can be any value that can be put through JSON.stringify()\n * to create a serialized representation of itself.\n */\n add: (element_reference) => {\n const plot = this.parent_plot;\n const element = deepCopy(element_reference);\n if (typeof element_reference == 'object' && typeof element.html != 'string') {\n element.html = ( (typeof element_reference.toHTML == 'function') ? element_reference.toHTML() : element_reference.toString());\n }\n // Check if the element is already in the model covariates array and return if it is.\n for (let i = 0; i < plot.state.model.covariates.length; i++) {\n if (JSON.stringify(plot.state.model.covariates[i]) === JSON.stringify(element)) {\n return plot;\n }\n }\n plot.state.model.covariates.push(element);\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Remove an element from `state.model.covariates` (and from the toolbar widget menu's\n * representation of the state model). When plot state is changed, this will automatically trigger\n * requests for new data accordingly.\n * @param {number} idx Array index of the element, in the `state.model.covariates array`.\n */\n removeByIdx: (idx) => {\n const plot = this.parent_plot;\n if (typeof plot.state.model.covariates[idx] == 'undefined') {\n throw new Error(`Unable to remove model covariate, invalid index: ${idx.toString()}`);\n }\n plot.state.model.covariates.splice(idx, 1);\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Empty the `state.model.covariates` array (and toolbar widget menu representation thereof) of all\n * elements. When plot state is changed, this will automatically trigger requests for new data accordingly\n */\n removeAll: () => {\n const plot = this.parent_plot;\n plot.state.model.covariates = [];\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Manually trigger the update methods on the toolbar widget's button and menu elements to force\n * display of most up-to-date content. Can be used to force the toolbar to reflect changes made, eg if\n * modifying `state.model.covariates` directly instead of via `plot.CovariatesModel`\n */\n updateWidget: () => {\n this.button.update();\n this.button.menu.update();\n },\n };\n }\n\n update() {\n\n if (this.button) {\n return this;\n }\n\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n\n this.button.menu.setPopulate(() => {\n const selector = this.button.menu.inner_selector;\n selector.html('');\n // General model HTML representation\n if (typeof this.parent_plot.state.model.html != 'undefined') {\n selector.append('div').html(this.parent_plot.state.model.html);\n }\n // Model covariates table\n if (!this.parent_plot.state.model.covariates.length) {\n selector.append('i').html('no covariates in model');\n } else {\n selector.append('h5').html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);\n const table = selector.append('table');\n this.parent_plot.state.model.covariates.forEach((covariate, idx) => {\n const html = ((typeof covariate == 'object' && typeof covariate.html == 'string') ? covariate.html : covariate.toString());\n const row = table.append('tr');\n row.append('td').append('button')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}`)\n .style('margin-left', '0em')\n .on('click', () => this.parent_plot.CovariatesModel.removeByIdx(idx))\n .html('×');\n row.append('td')\n .html(html);\n });\n selector.append('button')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}`)\n .style('margin-left', '4px')\n .html('× Remove All Covariates')\n .on('click', () => this.parent_plot.CovariatesModel.removeAll());\n }\n });\n\n this.button.preUpdate = () => {\n let html = 'Model';\n const count = this.parent_plot.state.model.covariates.length;\n if (count) {\n const noun = count > 1 ? 'covariates' : 'covariate';\n html += ` (${count} ${noun})`;\n }\n this.button.setHtml(html).disable(false);\n };\n\n this.button.show();\n\n return this;\n }\n }\n\n\n /**\n * Menu for manipulating multiple data layers in a single panel: show/hide, change order, etc.\n * @alias module:ext/lz-widget-addons~data_layers\n * @see module:LocusZoom_Widgets~BaseWidget\n */\n class DataLayersWidget extends _BaseWidget {\n update() {\n\n if (typeof this.layout.button_html != 'string') {\n this.layout.button_html = 'Data Layers';\n }\n if (typeof this.layout.button_title != 'string') {\n this.layout.button_title = 'Manipulate Data Layers (sort, dim, show/hide, etc.)';\n }\n\n if (this.button) {\n return this;\n }\n\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n this.parent_panel.data_layer_ids_by_z_index.slice().reverse().forEach((id, idx) => {\n const data_layer = this.parent_panel.data_layers[id];\n const name = (typeof data_layer.layout.name != 'string') ? data_layer.id : data_layer.layout.name;\n const row = table.append('tr');\n // Layer name\n row.append('td').html(name);\n // Status toggle buttons\n this.layout.statuses.forEach((status_adj) => {\n const status_idx = STATUS_ADJECTIVES.indexOf(status_adj);\n const status_verb = STATUS_VERBS[status_idx];\n let html, onclick, highlight;\n if (data_layer.global_statuses[status_adj]) {\n html = STATUS_ANTIVERBS[status_idx];\n onclick = `un${status_verb}AllElements`;\n highlight = '-highlighted';\n } else {\n html = STATUS_VERBS[status_idx];\n onclick = `${status_verb}AllElements`;\n highlight = '';\n }\n row.append('td').append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}${highlight}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer[onclick]();\n this.button.menu.populate();\n })\n .html(html);\n });\n // Sort layer buttons\n const at_top = (idx === 0);\n const at_bottom = (idx === (this.parent_panel.data_layer_ids_by_z_index.length - 1));\n const td = row.append('td');\n td.append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${at_bottom ? '-disabled' : ''}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer.moveBack(); this.button.menu.populate();\n })\n .html('▾')\n .attr('title', 'Move layer down (further back)');\n td.append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${at_top ? '-disabled' : ''}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer.moveForward(); this.button.menu.populate();\n })\n .html('▴')\n .attr('title', 'Move layer up (further front)');\n td.append('a')\n .attr('class', 'lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red')\n .style('margin-left', '0em')\n .on('click', () => {\n if (confirm(`Are you sure you want to remove the ${name} layer? This cannot be undone.`)) {\n data_layer.parent.removeDataLayer(id);\n }\n return this.button.menu.populate();\n })\n .html('×')\n .attr('title', 'Remove layer');\n });\n return this;\n });\n\n this.button.show();\n\n return this;\n }\n }\n\n const covariates_model_tooltip = function () {\n const covariates_model_association = LocusZoom.Layouts.get('tooltip', 'standard_association', { unnamespaced: true });\n covariates_model_association.html += 'Condition on Variant
';\n return covariates_model_association;\n }();\n\n const covariates_model_plot = function () {\n const covariates_model_plot_toolbar = LocusZoom.Layouts.get('toolbar', 'standard_association', { unnamespaced: true });\n covariates_model_plot_toolbar.widgets.push({\n type: 'covariates_model',\n button_html: 'Model',\n button_title: 'Show and edit covariates currently in model',\n position: 'left',\n });\n return covariates_model_plot_toolbar;\n }();\n\n LocusZoom.Widgets.add('covariates_model', CovariatesModel);\n LocusZoom.Widgets.add('data_layers', DataLayersWidget);\n\n LocusZoom.Layouts.add('tooltip', 'covariates_model_association', covariates_model_tooltip);\n LocusZoom.Layouts.add('toolbar', 'covariates_model_plot', covariates_model_plot);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/ext/lz-widget-addons.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","Math","sqrt","deepCopy","item","JSON","parse","stringify","STATUS_VERBS","STATUS_ADJECTIVES","STATUS_ANTIVERBS","install","LocusZoom","_Button","Widgets","_BaseWidget","covariates_model_tooltip","covariates_model_association","Layouts","html","covariates_model_plot","covariates_model_plot_toolbar","widgets","push","type","button_html","button_title","position","add","this","parent_plot","state","model","covariates","CovariatesModel","button","element_reference","plot","element","toHTML","toString","i","length","applyState","updateWidget","removeByIdx","idx","Error","splice","removeAll","update","menu","setColor","layout","color","setHtml","setTitle","setOnclick","populate","setPopulate","selector","inner_selector","append","table","forEach","covariate","row","attr","style","on","preUpdate","count","disable","show","parent_panel","_data_layer_ids_by_z_index","slice","reverse","id","data_layer","data_layers","name","statuses","status_adj","status_idx","indexOf","status_verb","onclick","highlight","_global_statuses","at_top","at_bottom","td","moveBack","moveForward","confirm","parent","removeDataLayer","use"],"mappings":"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","file":"ext/lz-widget-addons.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {}\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * Optional LocusZoom extension: must be included separately, and after LocusZoom has been loaded\n *\n * This contains (reusable) code to power some (rarely used) demo features:\n * - The \"covariates model\" demo, in which an LZ toolbar widget is populated\n * with information by selecting points on the plot (see \"covariates model\" demo)\n * - The \"data layers\" button, which allows fine control over multiple data layers shown in the same panel\n * (show/hide, fade, change order, etc). This is powerful, but rarely used because showing many datasets in a small\n * space makes data hard to see. (see \"multiple phenotypes layered\" demo)\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n *\n * ```javascript\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```javascript\n * import LocusZoom from 'locuszoom';\n * import WidgetAddons from 'locuszoom/esm/ext/lz-widget-addons';\n * LocusZoom.use(WidgetAddons);\n * ```\n *\n * Then use the features made available by this extension. (see demos and documentation for guidance)\n *\n * @module\n */\nimport {deepCopy} from '../helpers/layouts';\n\n// In order to work in a UMD context, this module imports the top-level LocusZoom symbol\n\nconst STATUS_VERBS = ['highlight', 'select', 'fade', 'hide'];\nconst STATUS_ADJECTIVES = ['highlighted', 'selected', 'faded', 'hidden'];\nconst STATUS_ANTIVERBS = ['unhighlight', 'deselect', 'unfade', 'show'];\n\n\n// LocusZoom plugins work by exporting a function that receives the `LocusZoom` object\n// This allows them to work in many contexts (including script tags and ES6 imports)\nfunction install(LocusZoom) {\n const _Button = LocusZoom.Widgets.get('_Button');\n const _BaseWidget = LocusZoom.Widgets.get('BaseWidget');\n\n\n /**\n * Special button/menu to allow model building by tracking individual covariants. Will track a list of covariate\n * objects and store them in the special `model.covariates` field of plot `state`.\n *\n * This is a prototype widget for building a conditional analysis model, but it performs no calculation\n * functionality beyond building a list of items.\n * @alias module:ext/lz-widget-addons~covariates_model\n * @see module:LocusZoom_Widgets~BaseWidget\n * @param {object} layout\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n */\n class CovariatesModel extends _BaseWidget {\n initialize() {\n // Initialize state.model.covariates\n this.parent_plot.state.model = this.parent_plot.state.model || {};\n this.parent_plot.state.model.covariates = this.parent_plot.state.model.covariates || [];\n // Create an object at the plot level for easy access to interface methods in custom client-side JS\n /**\n * When a covariates model toolbar element is present, create (one) object at the plot level that exposes\n * widget data and state for custom interactions with other plot elements.\n */\n this.parent_plot.CovariatesModel = {\n /** @member {Button} */\n button: this,\n /**\n * Add an element to the model and show a representation of it in the toolbar widget menu. If the\n * element is already part of the model, do nothing (to avoid adding duplicates).\n * When plot state is changed, this will automatically trigger requests for new data accordingly.\n * @param {string|object} element_reference Can be any value that can be put through JSON.stringify()\n * to create a serialized representation of itself.\n */\n add: (element_reference) => {\n const plot = this.parent_plot;\n const element = deepCopy(element_reference);\n if (typeof element_reference == 'object' && typeof element.html != 'string') {\n element.html = ( (typeof element_reference.toHTML == 'function') ? element_reference.toHTML() : element_reference.toString());\n }\n // Check if the element is already in the model covariates array and return if it is.\n for (let i = 0; i < plot.state.model.covariates.length; i++) {\n if (JSON.stringify(plot.state.model.covariates[i]) === JSON.stringify(element)) {\n return plot;\n }\n }\n plot.state.model.covariates.push(element);\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Remove an element from `state.model.covariates` (and from the toolbar widget menu's\n * representation of the state model). When plot state is changed, this will automatically trigger\n * requests for new data accordingly.\n * @param {number} idx Array index of the element, in the `state.model.covariates array`.\n */\n removeByIdx: (idx) => {\n const plot = this.parent_plot;\n if (typeof plot.state.model.covariates[idx] == 'undefined') {\n throw new Error(`Unable to remove model covariate, invalid index: ${idx.toString()}`);\n }\n plot.state.model.covariates.splice(idx, 1);\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Empty the `state.model.covariates` array (and toolbar widget menu representation thereof) of all\n * elements. When plot state is changed, this will automatically trigger requests for new data accordingly\n */\n removeAll: () => {\n const plot = this.parent_plot;\n plot.state.model.covariates = [];\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Manually trigger the update methods on the toolbar widget's button and menu elements to force\n * display of most up-to-date content. Can be used to force the toolbar to reflect changes made, eg if\n * modifying `state.model.covariates` directly instead of via `plot.CovariatesModel`\n */\n updateWidget: () => {\n this.button.update();\n this.button.menu.update();\n },\n };\n }\n\n update() {\n\n if (this.button) {\n return this;\n }\n\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n\n this.button.menu.setPopulate(() => {\n const selector = this.button.menu.inner_selector;\n selector.html('');\n // General model HTML representation\n if (typeof this.parent_plot.state.model.html != 'undefined') {\n selector.append('div').html(this.parent_plot.state.model.html);\n }\n // Model covariates table\n if (!this.parent_plot.state.model.covariates.length) {\n selector.append('i').html('no covariates in model');\n } else {\n selector.append('h5').html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);\n const table = selector.append('table');\n this.parent_plot.state.model.covariates.forEach((covariate, idx) => {\n const html = ((typeof covariate == 'object' && typeof covariate.html == 'string') ? covariate.html : covariate.toString());\n const row = table.append('tr');\n row.append('td').append('button')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}`)\n .style('margin-left', '0em')\n .on('click', () => this.parent_plot.CovariatesModel.removeByIdx(idx))\n .html('×');\n row.append('td')\n .html(html);\n });\n selector.append('button')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}`)\n .style('margin-left', '4px')\n .html('× Remove All Covariates')\n .on('click', () => this.parent_plot.CovariatesModel.removeAll());\n }\n });\n\n this.button.preUpdate = () => {\n let html = 'Model';\n const count = this.parent_plot.state.model.covariates.length;\n if (count) {\n const noun = count > 1 ? 'covariates' : 'covariate';\n html += ` (${count} ${noun})`;\n }\n this.button.setHtml(html).disable(false);\n };\n\n this.button.show();\n\n return this;\n }\n }\n\n\n /**\n * Menu for manipulating multiple data layers in a single panel: show/hide, change order, etc.\n * @alias module:ext/lz-widget-addons~data_layers\n * @see module:LocusZoom_Widgets~BaseWidget\n */\n class DataLayersWidget extends _BaseWidget {\n update() {\n\n if (typeof this.layout.button_html != 'string') {\n this.layout.button_html = 'Data Layers';\n }\n if (typeof this.layout.button_title != 'string') {\n this.layout.button_title = 'Manipulate Data Layers (sort, dim, show/hide, etc.)';\n }\n\n if (this.button) {\n return this;\n }\n\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n this.parent_panel._data_layer_ids_by_z_index.slice().reverse().forEach((id, idx) => {\n const data_layer = this.parent_panel.data_layers[id];\n const name = (typeof data_layer.layout.name != 'string') ? data_layer.id : data_layer.layout.name;\n const row = table.append('tr');\n // Layer name\n row.append('td').html(name);\n // Status toggle buttons\n this.layout.statuses.forEach((status_adj) => {\n const status_idx = STATUS_ADJECTIVES.indexOf(status_adj);\n const status_verb = STATUS_VERBS[status_idx];\n let html, onclick, highlight;\n if (data_layer._global_statuses[status_adj]) {\n html = STATUS_ANTIVERBS[status_idx];\n onclick = `un${status_verb}AllElements`;\n highlight = '-highlighted';\n } else {\n html = STATUS_VERBS[status_idx];\n onclick = `${status_verb}AllElements`;\n highlight = '';\n }\n row.append('td').append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}${highlight}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer[onclick]();\n this.button.menu.populate();\n })\n .html(html);\n });\n // Sort layer buttons\n const at_top = (idx === 0);\n const at_bottom = (idx === (this.parent_panel._data_layer_ids_by_z_index.length - 1));\n const td = row.append('td');\n td.append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${at_bottom ? '-disabled' : ''}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer.moveBack(); this.button.menu.populate();\n })\n .html('▾')\n .attr('title', 'Move layer down (further back)');\n td.append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${at_top ? '-disabled' : ''}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer.moveForward(); this.button.menu.populate();\n })\n .html('▴')\n .attr('title', 'Move layer up (further front)');\n td.append('a')\n .attr('class', 'lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red')\n .style('margin-left', '0em')\n .on('click', () => {\n if (confirm(`Are you sure you want to remove the ${name} layer? This cannot be undone.`)) {\n data_layer.parent.removeDataLayer(id);\n }\n return this.button.menu.populate();\n })\n .html('×')\n .attr('title', 'Remove layer');\n });\n return this;\n });\n\n this.button.show();\n\n return this;\n }\n }\n\n const covariates_model_tooltip = function () {\n const covariates_model_association = LocusZoom.Layouts.get('tooltip', 'standard_association');\n covariates_model_association.html += 'Condition on Variant
';\n return covariates_model_association;\n }();\n\n const covariates_model_plot = function () {\n const covariates_model_plot_toolbar = LocusZoom.Layouts.get('toolbar', 'standard_association');\n covariates_model_plot_toolbar.widgets.push({\n type: 'covariates_model',\n button_html: 'Model',\n button_title: 'Show and edit covariates currently in model',\n position: 'left',\n });\n return covariates_model_plot_toolbar;\n }();\n\n LocusZoom.Widgets.add('covariates_model', CovariatesModel);\n LocusZoom.Widgets.add('data_layers', DataLayersWidget);\n\n LocusZoom.Layouts.add('tooltip', 'covariates_model_association', covariates_model_tooltip);\n LocusZoom.Layouts.add('toolbar', 'covariates_model_plot', covariates_model_plot);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/locuszoom.app.min.js b/dist/locuszoom.app.min.js index 7368c255..a11e27da 100644 --- a/dist/locuszoom.app.min.js +++ b/dist/locuszoom.app.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.4 */ -var LocusZoom;(()=>{"use strict";var t={d:(e,s)=>{for(var i in s)t.o(s,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:s[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};(()=>{t.d(e,{default:()=>Je});var s={};t.r(s),t.d(s,{AssociationLZ:()=>_,BaseAdapter:()=>u,BaseApiAdapter:()=>p,ConnectorSource:()=>w,GeneConstraintLZ:()=>f,GeneLZ:()=>m,GwasCatalogLZ:()=>y,LDServer:()=>g,PheWASLZ:()=>v,RecombLZ:()=>b,StaticSource:()=>x});var i={};t.r(i),t.d(i,{htmlescape:()=>T,is_numeric:()=>L,log10:()=>M,logtoscinotation:()=>N,neglog10:()=>S,scinotation:()=>A,urlencode:()=>O});var a={};t.r(a),t.d(a,{BaseWidget:()=>st,_Button:()=>it,display_options:()=>mt,download:()=>rt,download_png:()=>lt,filter_field:()=>ot,menu:()=>_t,move_panel_down:()=>dt,move_panel_up:()=>ct,region_scale:()=>nt,remove_panel:()=>ht,resize_to_data:()=>gt,set_state:()=>ft,shift_region:()=>ut,title:()=>at,toggle_legend:()=>yt,zoom_region:()=>pt});var n={};t.r(n),t.d(n,{categorical_bin:()=>Pt,if_value:()=>Ot,interpolate:()=>It,numerical_bin:()=>Ct,ordinal_cycle:()=>Rt,stable_choice:()=>Dt});var o={};t.r(o),t.d(o,{BaseDataLayer:()=>Ft,annotation_track:()=>Gt,arcs:()=>Kt,category_scatter:()=>ie,genes:()=>Yt,highlight_regions:()=>Zt,line:()=>Xt,orthogonal_line:()=>te,scatter:()=>se});var r={};t.r(r),t.d(r,{data_layer:()=>Be,panel:()=>Ue,plot:()=>Fe,toolbar:()=>je,toolbar_widgets:()=>Ie,tooltip:()=>De});const l="0.13.4";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}function d(t,e,s){if(e&&s||!e&&!s)throw new Error(`${t} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(e&&!["GRCh37","GRCh38"].includes(e))throw new Error(`${t} must specify a valid genome build number`)}class u{constructor(t){this._enableCache=!0,this._cachedKey=null,this._cache_pos_start=null,this._cache_pos_end=null,this.__dependentSource=!1,this.parseInit(t)}parseInit(t){this.params=t.params||{}}getCacheKey(t,e,s){this.getURL(t,e,s);const i=t.chr,{_cache_pos_start:a,_cache_pos_end:n}=this;return a&&t.start>=a&&n&&t.end<=n?`${i}_${a}_${n}`:`${t.chr}_${t.start}_${t.end}`}getURL(t,e,s){return this.url}fetchRequest(t,e,s){const i=this.getURL(t,e,s);return fetch(i).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}getRequest(t,e,s){let i;const a=this.getCacheKey(t,e,s);return this._enableCache&&void 0!==a&&a===this._cachedKey?i=Promise.resolve(this._cachedResponse):(i=this.fetchRequest(t,e,s),this._enableCache&&(this._cachedKey=a,this._cache_pos_start=t.start,this._cache_pos_end=t.end,this._cachedResponse=i)),i}normalizeResponse(t){if(Array.isArray(t))return t;const e=Object.keys(t),s=t[e[0]].length;if(!e.every((function(e){return t[e].length===s})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const i=[],a=Object.keys(t);for(let e=0;ePromise.resolve(this.annotateData(t,e)))).then((t=>Promise.resolve(this.extractFields(t,s,i,a)))).then((t=>(e.discrete[n]=t,Promise.resolve(this.combineChainBody(t,e,s,i,a))))).then((t=>({header:e.header||{},discrete:e.discrete,body:t})))}getData(t,e,s,i){if(this.preGetData){const a=this.preGetData(t,e,s,i);this.pre&&(t=a.state||t,e=a.fields||e,s=a.outnames||s,i=a.trans||i)}return a=>this.__dependentSource&&a&&a.body&&!a.body.length?Promise.resolve(a):this.getRequest(t,a,e).then((t=>this.parseResponse(t,a,e,s,i)))}}class p extends u{parseInit(t){if(super.parseInit(t),this.url=t.url,!this.url)throw new Error("Source not initialized with required URL")}}class _ extends p{preGetData(t,e,s,i){return[this.params.id_field||"id","position"].forEach((function(t){e.includes(t)||(e.unshift(t),s.unshift(t),i.unshift(null))})),{fields:e,outnames:s,trans:i}}getURL(t,e,s){const i=e.header.analysis||this.params.source||this.params.analysis;if(void 0===i)throw new Error("Association source must specify an analysis ID to plot");return`${this.url}results/?filter=analysis in ${i} and chromosome in '${t.chr}' and position ge ${t.start} and position le ${t.end}`}normalizeResponse(t){return t=super.normalizeResponse(t),this.params&&this.params.sort&&t.length&&t[0].position&&t.sort((function(t,e){return t.position-e.position})),t}}class g extends p{constructor(t){super(t),this.__dependentSource=!0}preGetData(t,e){if(e.length>1&&(2!==e.length||!e.includes("isrefvar")))throw new Error(`LD does not know how to get all fields: ${e.join(", ")}`)}findMergeFields(t){let e={id:this.params.id_field,position:this.params.position_field,pvalue:this.params.pvalue_field,_names_:null};if(t&&t.body&&t.body.length>0){const i=Object.keys(t.body[0]),a=(s=i,function(){const t=arguments;for(let e=0;ee}:function(t,e){return t{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){a.data[e]=(a.data[e]||[]).concat(t.data[e])})),t.next?n(t.next):a}))};return n(i)}}class y extends p{constructor(t){super(t),this.__dependentSource=!0}getURL(t,e,s){const i=t.genome_build||this.params.build,a=this.params.source;d(this.constructor.name,i,a);const n=i?`&build=${i}`:` and id eq ${a}`;return`${this.url}?format=objects&sort=pos&filter=chrom eq '${t.chr}' and pos ge ${t.start} and pos le ${t.end}${n}`}findMergeFields(t){const e=Object.keys(t).find((function(t){return t.match(/\b(position|pos)\b/i)}));if(!e)throw new Error("Could not find data to align with GWAS catalog results");return{pos:e}}extractFields(t,e,s,i){return t}combineChainBody(t,e,s,i,a){if(!t.length)return e.body;const n="log_pvalue",o=i[s.indexOf(n)];function r(t,e,s,i,a){const r=t.n_catalog_matches||0;if(t.n_catalog_matches=r+1,!(t[o]&&t[o]>e[n]))for(let n=0;n25||"GRCh38"===i)return Promise.resolve({data:null});n=`{${n.join(" ")} }`;const o=this.getURL(t,e,s),r=JSON.stringify({query:n});return fetch(o,{method:"POST",body:r,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}combineChainBody(t,e,s,i,a){return t?(e.body.forEach((function(e){const s=`_${e.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=t[s]&&t[s].gnomad_constraint;i&&Object.keys(i).forEach((function(t){let s=i[t];void 0===e[t]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),e[t]=s)}))})),e.body):e}}class b extends p{getURL(t,e,s){const i=t.genome_build||this.params.build;let a=this.params.source;d(this.constructor.SOURCE_NAME,i,a);const n=i?`&build=${i}`:` and id in ${a}`;return`${this.url}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${n}`}}class x extends u{parseInit(t){this._data=t}getRequest(t,e,s){return Promise.resolve(this._data)}}class v extends p{getURL(t,e,s){const i=(t.genome_build?[t.genome_build]:null)||this.params.build;if(!i||!Array.isArray(i)||!i.length)throw new Error(["Adapter",this.constructor.SOURCE_NAME,"requires that you specify array of one or more desired genome build names"].join(" "));return[this.url,"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",i.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}getCacheKey(t,e,s){return this.getURL(t,e,s)}}class w extends u{constructor(t){if(super(t),!t||!t.sources)throw new Error("Connectors must specify the data they require as config.sources = {internal_name: chain_source_id}} pairs");this._source_name_mapping=t.sources;const e=Object.keys(t.sources);this._getRequiredSources().forEach((t=>{if(!e.includes(t))throw new Error(`Configuration for ${this.constructor.name} must specify a source ID corresponding to ${t}`)}))}parseInit(){}getRequest(t,e,s){return Object.keys(this._source_name_mapping).forEach((t=>{const s=this._source_name_mapping[t];if(e.discrete&&!e.discrete[s])throw new Error(`${this.constructor.name} cannot be used before loading required data for: ${s}`)})),Promise.resolve(e.body||[])}parseResponse(t,e,s,i,a){return Promise.resolve(this.combineChainBody(t,e,s,i,a)).then((function(t){return{header:e.header||{},discrete:e.discrete||{},body:t}}))}combineChainBody(t,e){throw new Error("This method must be implemented in a subclass")}_getRequiredSources(){throw new Error("Must specify an array that identifes the kind of data required by this source")}}const z=new c;for(let[t,e]of Object.entries(s))z.add(t,e);z.add("StaticJSON",x),z.add("LDLZ2",g);const $=z,k=d3,E={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function M(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function S(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function N(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function A(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function T(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function L(t){return"number"==typeof t}function O(t){return encodeURIComponent(t)}const C=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,e]of Object.entries(i))C.add(t,e);const P=C;class R{constructor(t){const e=/^(?:([^:]+):)?([^:|]*)(\|.+)*$/.exec(t);this.full_name=t,this.namespace=e[1]||null,this.name=e[2]||null,this.transformations=[],"string"==typeof e[3]&&e[3].length>1&&(this.transformations=e[3].substring(1).split("|"),this.transformations.forEach(((t,e)=>this.transformations[e]=P.get(t))))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[`${this.namespace}:${this.name}`]?s=t[`${this.namespace}:${this.name}`]:void 0!==t[this.name]?s=t[this.name]:e&&void 0!==e[this.full_name]&&(s=e[this.full_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const D=/^(\*|[\w]+)/,I=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function j(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=D.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=D.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=I.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function B(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function U(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const n=t[s.attr];1===e.length?void 0!==n&&a.push([s.attr]):a.push(...U(n,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...U(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...U(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[n,o]of Object.entries(t))a.push(...U(o,e).map((t=>[n].concat(t)))),"*"===s.attr&&a.push(...U(o,i).map((t=>[n].concat(t))))}}else if(s.attrs)for(let[e,n]of Object.entries(t)){const[t,o,r]=B(n,s.attrs);r===s.value&&a.push(...U(n,i).map((t=>[e].concat(t))))}const n=(o=a,r=JSON.stringify,[...new Map(o.map((t=>[r(t),t]))).values()]);var o,r;return n.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),n}function F(t,e){const s=function(t,e){let s=[];for(let i of U(t,e))s.push(B(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=j(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const q=Math.sqrt(3),G={draw(t,e){const s=-Math.sqrt(e/(3*q));t.moveTo(0,2*-s),t.lineTo(-q*s,s),t.lineTo(q*s,s),t.closePath()}};function H(t,e,s){if(e?"string"==typeof e&&(e={default:e}):e={default:""},"string"==typeof t){const i=/\{\{namespace(\[[A-Za-z_0-9]+\]|)\}\}/g;let a,n,o,r;const l=[];for(;null!==(a=i.exec(t));)n=a[0],o=a[1].length?a[1].replace(/(\[|\])/g,""):null,r=s,null!=e&&"object"==typeof e&&void 0!==e[o]&&(r=e[o]+(e[o].length?":":"")),l.push({base:n,namespace:r});for(let e in l)t=t.replace(l[e].base,l[e].namespace)}else if("object"==typeof t&&null!=t){if(void 0!==t.namespace){e=Z(e,"string"==typeof t.namespace?{default:t.namespace}:t.namespace)}let i,a;for(let n in t)"namespace"!==n&&(i=H(t[n],e,s),a=H(n,e,s),n!==a&&delete t[n],t[a]=i)}return t}function Z(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=Z(t[s],e[s])):t[s]=J(e[s])}return t}function J(t){return JSON.parse(JSON.stringify(t))}function K(t){if(!t)return null;if("triangledown"===t)return G;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return k[e]||null}function V(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>V(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,n)=>(a[n]=V(t[n],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const n=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(n,s)}}function Y(t,e,s){return function(t,e,s){return F(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function W(t,e){return function(t,e){return F(t,e).map((t=>t[2]))}(t,e)}const X=class{constructor(t){this._sources=t}__split_requests(t){var e={},s=/^(?:([^:]+):)?([^:|]*)(\|.+)*$/;return t.forEach((function(t){var i=s.exec(t),a=i[1]||"base",n=i[2],o=P.get(i[3]);void 0===e[a]&&(e[a]={outnames:[],fields:[],trans:[]}),e[a].outnames.push(t),e[a].fields.push(n),e[a].trans.push(o)})),e}getData(t,e){for(var s=this.__split_requests(e),i=Object.keys(s).map((e=>{if(!this._sources.get(e))throw new Error(`Datasource for namespace ${e} not found`);return this._sources.get(e).getData(t,s[e].fields,s[e].outnames,s[e].trans)})),a=Promise.resolve({header:{},body:[],discrete:{}}),n=0;n(this.curtain.showing||(this.curtain.selector=k.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&&et(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function tt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=k.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function et(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class st{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&&et(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class it{constructor(t){if(!(t instanceof st))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=k.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),n=this.menu.outer_selector.node().getBoundingClientRect(),o=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-n.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-n.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),p=Math.min(o+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${p}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(et,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class at extends st{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class nt extends st{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(St(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&&et(this.selector,this.layout.style),this}}class ot extends st{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class rt extends st{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new it(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=k.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+k.select(this).attr("dy").substring(-2).slice(0,-2);k.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class lt extends rt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((n,o)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{n(URL.createObjectURL(t))}))},r.src=t}))}))}}class ht extends st{update(){return this.button||(this.button=new it(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),k.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),k.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class ct extends st{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new it(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class dt extends st{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot.panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new it(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class ut extends st{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${St(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new it(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class pt extends st{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new it(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class _t extends st{update(){return this.button||(this.button=new it(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class gt extends st{constructor(t){super(...arguments)}update(){return this.button||(this.button=new it(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class yt extends st{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new it(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class mt extends st{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,n={};s.forEach((t=>{const e=a[t];void 0!==e&&(n[t]=J(e))})),this._selected_item="default",this.button=new it(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,o=(a,o,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==o[t];i.layout[t]=e?o[t]:n[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return o(r,n,"default"),a.options.forEach(((t,e)=>o(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class ft extends st{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new it(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,n)=>{const o=s.append("tr"),r=`${e}${n}`;o.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",n).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),o.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const bt=new c;for(let[t,e]of Object.entries(a))bt.add(t,e);const xt=bt;class vt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.dashboard&&this.parent.layout.dashboard.components||this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{try{const e=xt.create(t.type,t,this);this.widgets.push(e)}catch(t){console.warn("Failed to create widget"),console.error(t)}})),"panel"===this.type&&k.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot.panel_boundaries.dragging||this.parent_plot.interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=k.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=k.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const wt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:12,hidden:!1};class zt{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=Z(this.parent.layout.legend||{},wt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent.data_layer_ids_by_z_index.slice().reverse().forEach((a=>{Array.isArray(this.parent.data_layers[a].layout.legend)&&this.parent.data_layers[a].layout.legend.forEach((a=>{const n=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),o=+a.label_size||+this.layout.label_size||12;let r=0,l=o/2+t/2;i=Math.max(i,o+t);const h=a.shape||"",c=K(h);if("line"===h){const e=+a.length||16,s=o/4+t/2;n.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(et,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;n.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(et,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if(c){const e=+a.size||40,s=Math.ceil(Math.sqrt(e/Math.PI));n.append("path").attr("class",a.class||"").attr("d",k.symbol().size(e).type(c)).attr("transform",`translate(${s}, ${s+t/2})`).attr("fill",a.color||{}).call(et,a.style||{}),r=2*s+t,l=Math.max(2*s+t/2,l),i=Math.max(i,2*s+t)}n.append("text").attr("text-anchor","left").attr("class","lz-label").attr("x",r).attr("y",l).style("font-size",o).text(a.label);const d=n.node().getBoundingClientRect();if("vertical"===this.layout.orientation)s+=d.height+t,i=0;else{const a=this.layout.origin.x+e+d.width;e>t&&a>this.parent.parent.layout.width&&(s+=i,e=t,n.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(n)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const $t={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class kt{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"==typeof t.id&&t.id.length){if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`)}else if(this.parent){const e=()=>{let t=`p${Math.floor(Math.random()*Math.pow(10,8))}`;return null!==t&&void 0===this.parent.panels[t]||(t=e()),t};t.id=e()}else t.id=`p${Math.floor(Math.random()*Math.pow(10,8))}`;this.id=t.id,this.initialized=!1,this.layout_idx=null,this.svg={},this.layout=Z(t||{},$t),this.parent?(this.state=this.parent.state,this.state_id=this.id,this.state[this.state_id]=this.state[this.state_id]||{}):(this.state=null,this.state_id=null),this.data_layers={},this.data_layer_ids_by_z_index=[],this.data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this.zoom_timeout=null,this.event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this.event_hooks[t]||(this.event_hooks[t]=[]),this.event_hooks[t].push(e),e}off(t,e){const s=this.event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this.event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this.event_hooks[t]&&this.event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=Z(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(et,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id [${t.id}]; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=ne.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this.data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this.data_layer_ids_by_z_index.length+e.layout.z_index,0)),this.data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this.data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this.data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id].layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){if(!this.data_layers[t])throw new Error(`Unable to remove data layer, ID not found: ${t}`);return this.data_layers[t].destroyAllTooltips(),this.data_layers[t].svg.container&&this.data_layers[t].svg.container.remove(),this.layout.data_layers.splice(this.data_layers[t].layout_idx,1),delete this.state[this.data_layers[t].state_id],delete this.data_layers[t],this.data_layer_ids_by_z_index.splice(this.data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id].layout_idx=e})),this}clearSelections(){return this.data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.inner_border.attr("x",this.layout.margin.left).attr("y",this.layout.margin.top).attr("width",this.parent_plot.layout.width-(this.layout.margin.left+this.layout.margin.right)).attr("height",this.layout.height-(this.layout.margin.top+this.layout.margin.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const t=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),n=Math.pow(10,e);return t===1/0&&(t=n),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,n),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},e={};if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};this.layout.axes.x.range&&(t.start=this.layout.axes.x.range.start||t.start,t.end=this.layout.axes.x.range.end||t.end),e.x=[t.start,t.end],e.x_shifted=[t.start,t.end]}if(this.y1_extent){const t={start:this.layout.cliparea.height,end:0};this.layout.axes.y1.range&&(t.start=this.layout.axes.y1.range.start||t.start,t.end=this.layout.axes.y1.range.end||t.end),e.y1=[t.start,t.end],e.y1_shifted=[t.start,t.end]}if(this.y2_extent){const t={start:this.layout.cliparea.height,end:0};this.layout.axes.y2.range&&(t.start=this.layout.axes.y2.range.start||t.start,t.end=this.layout.axes.y2.range.end||t.end),e.y2=[t.start,t.end],e.y2_shifted=[t.start,t.end]}if(this.parent.interaction.panel_id&&(this.parent.interaction.panel_id===this.id||this.parent.interaction.linked_panel_ids.includes(this.id))){let s,i=null;if(this.parent.interaction.zooming&&"function"==typeof this.x_scale){const t=Math.abs(this.x_extent[1]-this.x_extent[0]),i=Math.round(this.x_scale.invert(e.x_shifted[1]))-Math.round(this.x_scale.invert(e.x_shifted[0]));let a=this.parent.interaction.zooming.scale;const n=Math.floor(i*(1/a));a<1&&!isNaN(this.parent.layout.max_region_scale)?a=1/(Math.min(n,this.parent.layout.max_region_scale)/i):a>1&&!isNaN(this.parent.layout.min_region_scale)&&(a=1/(Math.max(n,this.parent.layout.min_region_scale)/i));const o=Math.floor(t*a);s=this.parent.interaction.zooming.center-this.layout.margin.left-this.layout.origin.x;const r=s/this.layout.cliparea.width,l=Math.max(Math.floor(this.x_scale.invert(e.x_shifted[0])-(o-i)*r),1);e.x_shifted=[this.x_scale(l),this.x_scale(l+o)]}else if(this.parent.interaction.dragging)switch(this.parent.interaction.dragging.method){case"background":e.x_shifted[0]=+this.parent.interaction.dragging.dragged_x,e.x_shifted[1]=this.layout.cliparea.width+this.parent.interaction.dragging.dragged_x;break;case"x_tick":k.event&&k.event.shiftKey?(e.x_shifted[0]=+this.parent.interaction.dragging.dragged_x,e.x_shifted[1]=this.layout.cliparea.width+this.parent.interaction.dragging.dragged_x):(s=this.parent.interaction.dragging.start_x-this.layout.margin.left-this.layout.origin.x,i=t(s/(s+this.parent.interaction.dragging.dragged_x),3),e.x_shifted[0]=0,e.x_shifted[1]=Math.max(this.layout.cliparea.width*(1/i),1));break;case"y1_tick":case"y2_tick":{const a=`y${this.parent.interaction.dragging.method[1]}_shifted`;k.event&&k.event.shiftKey?(e[a][0]=this.layout.cliparea.height+this.parent.interaction.dragging.dragged_y,e[a][1]=+this.parent.interaction.dragging.dragged_y):(s=this.layout.cliparea.height-(this.parent.interaction.dragging.start_y-this.layout.margin.top-this.layout.origin.y),i=t(s/(s-this.parent.interaction.dragging.dragged_y),3),e[a][0]=this.layout.cliparea.height,e[a][1]=this.layout.cliparea.height-this.layout.cliparea.height*(1/i))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=k.scaleLinear().domain(this[`${t}_extent`]).range(e[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(e[t][0]),this[`${t}_scale`].invert(e[t][1])],this[`${t}_scale`]=k.scaleLinear().domain(this[`${t}_extent`]).range(e[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!k.event.shiftKey&&!k.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(k.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=k.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,k.event.wheelDelta||-k.event.detail||-k.event.deltaY));0!==e&&(this.parent.interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),this.parent.interaction.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this.zoom_timeout&&clearTimeout(this.zoom_timeout),this.zoom_timeout=setTimeout((()=>{this.parent.interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this.data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this.initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this.data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{Object.keys(this.layout.axes[t]).length&&!1!==this.layout.axes[t].render?(this.layout.axes[t].render=!0,this.layout.axes[t].label=this.layout.axes[t].label||null):this.layout.axes[t].render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),this.layout.height=Math.max(Math.round(+e),this.layout.min_height)),this.layout.cliparea.width=Math.max(this.parent_plot.layout.width-(this.layout.margin.left+this.layout.margin.right),0),this.layout.cliparea.height=Math.max(this.layout.height-(this.layout.margin.top+this.layout.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",this.layout.height),this.initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this.initialized&&this.render(),this}setMargin(t,e,s,i){let a;return!isNaN(t)&&t>=0&&(this.layout.margin.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.margin.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(this.layout.margin.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(this.layout.margin.left=Math.max(Math.round(+i),0)),this.layout.margin.top+this.layout.margin.bottom>this.layout.height&&(a=Math.floor((this.layout.margin.top+this.layout.margin.bottom-this.layout.height)/2),this.layout.margin.top-=a,this.layout.margin.bottom-=a),this.layout.margin.left+this.layout.margin.right>this.parent_plot.layout.width&&(a=Math.floor((this.layout.margin.left+this.layout.margin.right-this.parent_plot.layout.width)/2),this.layout.margin.left-=a,this.layout.margin.right-=a),["top","right","bottom","left"].forEach((t=>{this.layout.margin[t]=Math.max(this.layout.margin[t],0)})),this.layout.cliparea.width=Math.max(this.parent_plot.layout.width-(this.layout.margin.left+this.layout.margin.right),0),this.layout.cliparea.height=Math.max(this.layout.height-(this.layout.margin.top+this.layout.margin.bottom),0),this.layout.cliparea.origin.x=this.layout.margin.left,this.layout.cliparea.origin.y=this.layout.margin.top,this.initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=Q.call(this),this.loader=tt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new vt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this.data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new zt(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this.data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(k.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent.panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){return this.parent.panel_ids_by_y_index[this.layout.y_index-1]&&(this.parent.panel_ids_by_y_index[this.layout.y_index]=this.parent.panel_ids_by_y_index[this.layout.y_index-1],this.parent.panel_ids_by_y_index[this.layout.y_index-1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}moveDown(){return this.parent.panel_ids_by_y_index[this.layout.y_index+1]&&(this.parent.panel_ids_by_y_index[this.layout.y_index]=this.parent.panel_ids_by_y_index[this.layout.y_index+1],this.parent.panel_ids_by_y_index[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this.data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this.data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this.data_promises).then((()=>{this.initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=k.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=k.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this.data_layer_ids_by_z_index.reduce(((e,a)=>{const n=s.data_layers[a];return e.concat(n.getTicks(t,i))}),[]).map((t=>{let s={};return s=Z(s,e),Z(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,n=1.5,o=.5+1.5*n,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(p=parseFloat(p.toFixed(c)));u.push(p),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eSt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=k.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=k.select(this).select("text");s[`${t}_ticks`][i].style&&et(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const n=this.layout.axes[t].label||null;return null!==n&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(At(n,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof k.select(this).node().focus&&k.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";k.event&&k.event.shiftKey&&(i="move"),k.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){k.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this.data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this.data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}E.verbs.forEach(((t,e)=>{const s=E.adjectives[e],i=`un${t}`;kt.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},kt.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Et={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Mt{constructor(t,e,s){this.initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this.panel_ids_by_y_index=[],this.remap_promises=[],this.layout=s,Z(this.layout,Et),this._base_layout=J(this.layout),this.state=this.layout.state,this.lzd=new X(e),this._external_listeners=new Map,this.event_hooks={},this.interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this.event_hooks[t]||(this.event_hooks[t]=[]),this.event_hooks[t].push(e),e}off(t,e){const s=this.event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this.event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this.event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this.event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new kt(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this.panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this.panel_ids_by_y_index.length+e.layout.y_index,0)),this.panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this.panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id].layout_idx=s,this.initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t].data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i.layer_state,delete this.layout.state[i.state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){if(!this.panels[t])throw new Error(`Unable to remove panel, ID not found: ${t}`);return this.panel_boundaries.hide(),this.clearPanelData(t),this.panels[t].loader.hide(),this.panels[t].toolbar.destroy(!0),this.panels[t].curtain.hide(),this.panels[t].svg.container&&this.panels[t].svg.container.remove(),this.layout.panels.splice(this.panels[t].layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id].layout_idx=e})),this.panel_ids_by_y_index.splice(this.panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this.initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e,s){const i=(s=s||{}).onerror||function(t){console.log("An error occurred while acting on an external callback",t)},a=()=>{try{this.lzd.getData(this.state,t).then((t=>e(s.discrete?t.discrete:t.body,this))).catch(i)}catch(t){i(t)}};return this.on("data_rendered",a),a}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this.remap_promises=[],this.loading_data=!0;for(let t in this.panels)this.remap_promises.push(this.panels[t].reMap());return Promise.all(this.remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this.panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e.data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this.initialized=!1,this.svg=null,this.panels=null}_canInteract(t){return(t=t||null)?(void 0===this.interaction.panel_id||this.interaction.panel_id===t)&&!this.loading_data:!(this.interaction.dragging||this.interaction.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==k.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this.panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");if(this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.responsive_resize){const t=()=>this.rescaleSVG();window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t);const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}return this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this.panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,n=e.layout.height*s;e.setDimensions(a,n),e.setOrigin(0,i),i+=n,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this.initialized&&(this.panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e in this.panels)this.panels[e].layout.interaction.x_linked&&(t.left=Math.max(t.left,this.panels[e].layout.margin.left),t.right=Math.max(t.right,this.panels[e].layout.margin.right));let e=0;return this.panel_ids_by_y_index.forEach((s=>{const i=this.panels[s];if(i.setOrigin(0,e),e+=this.panels[s].layout.height,i.layout.interaction.x_linked){const e=Math.max(t.left-i.layout.margin.left,0)+Math.max(t.right-i.layout.margin.right,0);i.layout.width+=e,i.layout.margin.left=t.left,i.layout.margin.right=t.right,i.layout.cliparea.origin.x=t.left}})),this.setDimensions(),this.panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize&&k.select(this.container).classed("lz-container-responsive",!0),this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this.mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=Q.call(this),this.loader=tt.call(this),this.panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent.panel_ids_by_y_index.forEach(((t,e)=>{const s=k.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=k.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent.panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+k.event.dy);const i=t.layout.height-s;this.parent.panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent.panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent.panel_boundaries.selectors.push(s)}));const t=k.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=k.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+k.event.dx,this.parent._total_height+k.event.dy)})),t.call(e),this.parent.panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent.panel_ids_by_y_index[s]],a=i._getPageOrigin(),n=t.x,o=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${o}px`).style("left",`${n}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&k.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this.panel_boundaries.hide_timeout),this.panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this.panel_boundaries.hide_timeout=setTimeout((()=>{this.panel_boundaries.hide()}),300)})),this.toolbar=new vt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this.mouse_guide.vertical.attr("x",-1),this.mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=k.mouse(this.svg.node());this.mouse_guide.vertical.attr("x",t[0]),this.mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{if(this.interaction.dragging){const t=k.mouse(this.svg.node());k.event&&k.event.preventDefault(),this.interaction.dragging.dragged_x=t[0]-this.interaction.dragging.start_x,this.interaction.dragging.dragged_y=t[1]-this.interaction.dragging.start_y,this.panels[this.interaction.panel_id].render(),this.interaction.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=k.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this.initialized=!0;const a=this.svg.node().getBoundingClientRect(),n=a.width?a.width:this.layout.width,o=a.height?a.height:this._total_height;return this.setDimensions(n,o),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof kt&&s&&this._canInteract()))return this.stopDrag();const i=k.mouse(this.svg.node());return this.interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){if(!this.interaction.dragging)return this;if("object"!=typeof this.panels[this.interaction.panel_id])return this.interaction={},this;const t=this.panels[this.interaction.panel_id],e=(e,s,i)=>{t.data_layer_ids_by_z_index.forEach((a=>{const n=t.data_layers[a].layout[`${e}_axis`];n.axis===s&&(n.floor=i[0],n.ceiling=i[1],delete n.lower_buffer,delete n.upper_buffer,delete n.min_extent,delete n.ticks)}))};switch(this.interaction.dragging.method){case"background":case"x_tick":0!==this.interaction.dragging.dragged_x&&(e("x",1,t.x_extent),this.applyState({start:t.x_extent[0],end:t.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==this.interaction.dragging.dragged_y){const s=parseInt(this.interaction.dragging.method[1]);e("y",s,t[`y${s}_extent`])}}return this.interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function St(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),n=Math.min(Math.max(e,0),2),o=Math.min(Math.max(a,n),12);let r=`${(t/Math.pow(10,e)).toFixed(o)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Nt(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function At(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([A-Za-z0-9_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const n=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(n())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},o=[];for(;i.length>0;)o.push(n());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new R(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return o.map(l).join("")}const Tt=new h;Tt.add("=",((t,e)=>t===e)),Tt.add("!=",((t,e)=>t!=e)),Tt.add("<",((t,e)=>tt<=e)),Tt.add(">",((t,e)=>t>e)),Tt.add(">=",((t,e)=>t>=e)),Tt.add("%",((t,e)=>t%e)),Tt.add("in",((t,e)=>e&&e.includes(t))),Tt.add("match",((t,e)=>t&&t.includes(e)));const Lt=Tt,Ot=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Ct=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Rt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Dt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let n=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var n=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(n=i)})),null===n)return a;const t=(+e-s[n-1])/(s[n]-s[n-1]);return isFinite(t)?k.interpolate(i[n-1],i[n])(t):a}},jt=new h;for(let[t,e]of Object.entries(n))jt.add(t,e);jt.add("if",Ot);const Bt=jt,Ut={id:"",type:"",tag:"custom_data_type",fields:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class Ft{constructor(t,e){this.initialized=!1,this.layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=Z(t||{},Ut),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=J(this.layout),this.state={},this.state_id=null,this.layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this.tooltips={}),this.global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1}}render(){throw new Error("Method must be implemented")}moveForward(){return this.parent.data_layer_ids_by_z_index[this.layout.z_index+1]&&(this.parent.data_layer_ids_by_z_index[this.layout.z_index]=this.parent.data_layer_ids_by_z_index[this.layout.z_index+1],this.parent.data_layer_ids_by_z_index[this.layout.z_index+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){return this.parent.data_layer_ids_by_z_index[this.layout.z_index-1]&&(this.parent.data_layer_ids_by_z_index[this.layout.z_index]=this.parent.data_layer_ids_by_z_index[this.layout.z_index-1],this.parent.data_layer_ids_by_z_index[this.layout.z_index-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this.layer_state.extra_fields[i]||(this.layer_state.extra_fields[i]={}),this.layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}_getDataExtent(t,e){return t=t||this.data,k.extent(t,(t=>+new R(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field||"id";if(void 0===t[s])throw new Error("Unable to generate element ID");const i=t[s].toString().replace(/\W/g,""),a=`${this.getBaseId()}-${i}`.replace(/([:.[\],])/g,"_");return t[e]=a,a}getElementStatusNodeId(t){return null}getElementById(t){const e=k.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Lt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new R(t):null;return this.data.forEach(((a,n)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.toHTML=()=>{const t=this.layout.id_field||"id";let e="";return a[t]&&(e=a[t].toString()),e},a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null},a.deselect=()=>{this.getDataLayer().unselectElement(this)}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(_+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=p<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-p,0),e=Math.max(c.width/2+p-u,0);y=h.x+p-c.width/2-e+t,b=h.x+p-y-7,"top"===w?(g=h.y+_-(v+c.height+8),m="down",f=c.height-1):(g=h.y+_+v+8,m="up",f=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+p+x+8,m="left",b=-8):(y=h.x+p-c.width-x-8,m="right",b=c.width-1),_-c.height/2<=0?(g=h.y+_-10.5-6,f=6):_+c.height/2>=d?(g=h.y+_+7+6-c.height,f=c.height-14-6):(g=h.y+_-c.height/2,f=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${m}`).style("left",`${b}px`).style("top",`${f}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:n}=t,o=Lt.get(i),r=this.getElementAnnotation(e);o(s?new R(s).resolve(e,r):e,n)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this.layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;E.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this.state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this.state_id]=t),this.layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this.tooltips[e])return this.tooltips[e]={data:t,arrow:null,selector:k.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this.layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this.tooltips[e].selector.html(""),this.tooltips[e].arrow=null,this.layout.tooltip.html&&this.tooltips[e].selector.html(At(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this.tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this.tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this.tooltips[s]&&("object"==typeof this.tooltips[s].selector&&this.tooltips[s].selector.remove(),delete this.tooltips[s]),!e){this.layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this.tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this.tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this.tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this.tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){if("object"!=typeof this.layout.tooltip)return this;const s=this.getElementId(t),i=(t,e,s)=>{let a=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",a=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let n;for(let o in e)n=i(t,e[o],o),null===a?a=n:"and"===s?a=a&&n:"or"===s&&(a=a||n)}}return a};let a={};"string"==typeof this.layout.tooltip.show?a={and:[this.layout.tooltip.show]}:"object"==typeof this.layout.tooltip.show&&(a=this.layout.tooltip.show);let n={};"string"==typeof this.layout.tooltip.hide?n={and:[this.layout.tooltip.hide]}:"object"==typeof this.layout.tooltip.hide&&(n=this.layout.tooltip.hide);const o=this.layer_state;var r={};E.adjectives.forEach((t=>{const e=`un${t}`;r[t]=o.status_flags[t].has(s),r[e]=!r[t]}));const l=i(r,a),h=i(r,n),c=o.status_flags.has_tooltip.has(s);return!l||!e&&!c||h?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),k.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const n=this.getElementStatusNodeId(e);null!==n&&k.select(`#${n}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const o=!this.layer_state.status_flags[t].has(a);s&&o&&this.layer_state.status_flags[t].add(a),s||o||this.layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,o),o&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!o&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!o&&s||this.parent.emit("match_requested",{value:new R(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!E.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this.layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this.layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this.layer_state.status_flags[t]=new Set}return this.global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||k.select(k.event.target).datum(),s===!!k.event.ctrlKey&&i===!!k.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a.layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=At(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this.layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e.state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this.layout.fields).then((t=>{this.data=t.body,this.applyDataMethods(),this.initialized=!0}))}}E.verbs.forEach(((t,e)=>{const s=E.adjectives[e],i=`un${t}`;Ft.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},Ft.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},Ft.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Ft.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const qt={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class Gt extends Ft{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");Z(t,qt),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],n=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+n)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const Ht={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class Zt extends Ft{constructor(t){if(Z(t,Ht),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions&&t.regions.length&&t.fields&&t.fields.length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>k.ascending(t[s],e[s])||k.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,n){const o=a[a.length-1]||t;if(t[s]===o[s]&&t[i]<=o[e]){const s=Math.min(o[i],t[i]),n=Math.max(o[e],t[e]);t=Object.assign({},o,t,{[i]:s,[e]:n}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const Jt={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class Kt extends Ft{constructor(t){t=Z(t,Jt),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function n(t){const a=t[e.x_axis.field1],n=t[e.x_axis.field2],o=(a+n)/2,r=[[s(a),i(0)],[s(o),i(t[e.y_axis.field])],[s(n),i(0)]];return k.line().x((t=>t[0])).y((t=>t[1])).curve(k.curveNatural)(r)}const o=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(et,e.style),o.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(o).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>n(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>n(t))),r.exit().remove(),o.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],n=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:n(t.data[s.y_axis.field]),y_max:n(0)}}}const Vt={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:12,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class Yt extends Ft{constructor(t){t=Z(t,Vt),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=k.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const n=k.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,n.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(n).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),n.exit().remove();const o=k.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));o.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(o).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),o.exit().remove();const r=k.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=k.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=k.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const Wt={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5};class Xt extends Ft{constructor(t){if((t=Z(t,Wt)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const n=t.x_scale,o=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?k.area().x((t=>+n(t[e]))).y0(+o(0)).y1((t=>+o(t[s]))):k.line().x((t=>+n(t[e]))).y((t=>+o(t[s]))).curve(k[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(et,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!E.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this.layer_state.status_flags[t])return this;void 0===e&&(e=!0),this.global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this.global_statuses).forEach((t=>{this.global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const Qt={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class te extends Ft{constructor(t){t=Z(t,Qt),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments),this.data=[]}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],n=k.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const n=+t[e](s.y);return isNaN(n)?a[i]:n}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",n).call(et,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=k.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ee={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class se extends Ft{constructor(t){(t=Z(t,ee)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),n=Math.sqrt(a/Math.PI);return{x_min:e-n,x_max:e+n,y_min:i-n,y_max:i+n}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,n=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,o=(t,a)=>{const n=+t.attr("x"),o=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",n-o),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",n+o),i&&a.attr("x2",r+l))};t.label_texts.each((function(e,a){const r=k.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>n){const e=i?k.select(t.label_lines.nodes()[a]):null;o(r,e)}})),t.label_texts.each((function(e,n){const r=k.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?k.select(t.label_lines.nodes()[n]):null;t.label_texts.each((function(){const t=k.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(o(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=o.attr("y"),c=.5*(r.top_?(g=d-+n,d=+n,u-=g):u+l.height/2>_&&(g=u-+h,u=+h,d-=g),a.attr("y",d),o.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t.label_texts.nodes();t.label_lines.attr("y2",((t,s)=>k.select(e[s]).attr("y")))}this.seperate_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let n=this._applyFilters();if(n.forEach((t=>{let n=e(t[this.layout.x_axis.field]),o=s(t[this.layout.y_axis.field]);isNaN(n)&&(n=-1e3),isNaN(o)&&(o=-1e3),t[i]=n,t[a]=o})),this.layout.coalesce.active&&n.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:o,x_gap:r,y_gap:l}=this.layout.coalesce;n=function(t,e,s,i,a,n,o){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function p(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function _(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],m=g>=e&&g<=s&&y>=a&&y<=n;t.lz_is_match||!m?(p(),r.push(t)):null===c?_(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=o?u.push(t):(p(),_(g,y,t))})),p(),r}(n,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(o)?s(+o):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=n.filter(t)}else e=n;this.label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const o=`lz-data_layer-${this.layout.type}-label`,r=this.label_groups.enter().append("g").attr("class",o);this.label_texts&&this.label_texts.remove(),this.label_texts=this.label_groups.merge(r).append("text").text((e=>At(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(et,t.layout.label.style||{}),t.layout.label.lines&&(this.label_lines&&this.label_lines.remove(),this.label_lines=this.label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(et,t.layout.label.lines.style||{})),this.label_groups.exit().remove()}else this.label_texts&&this.label_texts.remove(),this.label_lines&&this.label_lines.remove(),this.label_groups&&this.label_groups.remove();const o=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(n,(t=>t[this.layout.id_field])),r=k.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>K(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;o.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(o).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),o.exit().remove(),this.layout.label&&(this.flip_labels(),this.seperate_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=k.select(k.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class ie extends se{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],n="string"==typeof i?i.toLowerCase():i,o="string"==typeof a?a.toLowerCase():a;return n===o?0:n{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],n=i[e],o=s[a]||[n,n];s[a]=[Math.min(o[0],n),Math.max(o[1],n)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const o=i[t];let r;switch(s){case"left":r=o[0];break;case"center":const t=o[1]-o[0];r=o[0]+(0!==t?t:o[0])/2;break;case"right":r=o[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const ae=new c;for(let[t,e]of Object.entries(o))ae.add(t,e);const ne=ae,oe=7.301,re={namespace:{assoc:"assoc"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{{{namespace[assoc]}}variant|htmlescape}}
\n P Value: {{{{namespace[assoc]}}log_pvalue|logtoscinotation|htmlescape}}
\n Ref. Allele: {{{{namespace[assoc]}}ref_allele|htmlescape}}
\n {{#if {{namespace[ld]}}isrefvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
'},le=function(){const t=J(re);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),he={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

{{gene_name|htmlescape}}

Gene ID: {{gene_id|htmlescape}}
Transcript ID: {{transcript_id|htmlescape}}
{{#if pLI}}
ConstraintExpected variantsObserved variantsConst. Metric
Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

{{/if}}More data on gnomAD'},ce={namespace:{assoc:"assoc",catalog:"catalog"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{{{namespace[catalog]}}variant|htmlescape}}
Catalog entries: {{n_catalog_matches|htmlescape}}
Top Trait: {{{{namespace[catalog]}}trait|htmlescape}}
Top P Value: {{{{namespace[catalog]}}log_pvalue|logtoscinotation}}
More: GWAS catalog / dbSNP'},de={namespace:{access:"access"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
{{{{namespace[access]}}start1|htmlescape}}-{{{{namespace[access]}}end1|htmlescape}}
Promoter
{{{{namespace[access]}}start2|htmlescape}}-{{{{namespace[access]}}end2|htmlescape}}
{{#if {{namespace[access]}}target}}Target: {{{{namespace[access]}}target|htmlescape}}
{{/if}}Score: {{{{namespace[access]}}score|htmlescape}}"},ue={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:oe},pe={namespace:{recomb:"recomb"},id:"recombrate",type:"line",tag:"recombination",fields:["{{namespace[recomb]}}position","{{namespace[recomb]}}recomb_rate"],z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"{{namespace[recomb]}}position"},y_axis:{axis:2,field:"{{namespace[recomb]}}recomb_rate",floor:0,ceiling:100}},_e={namespace:{assoc:"assoc",ld:"ld"},id:"associationpvalues",type:"scatter",tag:"association",fields:["{{namespace[assoc]}}variant","{{namespace[assoc]}}position","{{namespace[assoc]}}log_pvalue","{{namespace[assoc]}}log_pvalue|logtoscinotation","{{namespace[assoc]}}ref_allele","{{namespace[ld]}}state","{{namespace[ld]}}isrefvar"],id_field:"{{namespace[assoc]}}variant",coalesce:{active:!0},point_shape:{scale_function:"if",field:"{{namespace[ld]}}isrefvar",parameters:{field_value:1,then:"diamond",else:"circle"}},point_size:{scale_function:"if",field:"{{namespace[ld]}}isrefvar",parameters:{field_value:1,then:80,else:40}},color:[{scale_function:"if",field:"{{namespace[ld]}}isrefvar",parameters:{field_value:1,then:"#9632b8"}},{scale_function:"numerical_bin",field:"{{namespace[ld]}}state",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{shape:"diamond",color:"#9632b8",size:40,label:"LD Ref Var",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(219, 61, 17)",size:40,label:"1.0 > r² ≥ 0.8",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(248, 195, 42)",size:40,label:"0.8 > r² ≥ 0.6",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(110, 254, 104)",size:40,label:"0.6 > r² ≥ 0.4",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(38, 188, 225)",size:40,label:"0.4 > r² ≥ 0.2",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(70, 54, 153)",size:40,label:"0.2 > r² ≥ 0.0",class:"lz-data_layer-scatter"},{shape:"circle",color:"#AAAAAA",size:40,label:"no r² data",class:"lz-data_layer-scatter"}],label:null,z_index:2,x_axis:{field:"{{namespace[assoc]}}position"},y_axis:{axis:1,field:"{{namespace[assoc]}}log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:J(re)},ge={namespace:{access:"access"},id:"coaccessibility",type:"arcs",tag:"coaccessibility",fields:["{{namespace[access]}}start1","{{namespace[access]}}end1","{{namespace[access]}}start2","{{namespace[access]}}end2","{{namespace[access]}}id","{{namespace[access]}}target","{{namespace[access]}}score"],match:{send:"{{namespace[access]}}target",receive:"{{namespace[access]}}target"},id_field:"{{namespace[access]}}id",filters:[{field:"{{namespace[access]}}score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"{{namespace[access]}}start1",field2:"{{namespace[access]}}start2"},y_axis:{axis:1,field:"{{namespace[access]}}score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:J(de)},ye=function(){let t=J(_e);return t=Z({id:"associationpvaluescatalog",fill_opacity:.7},t),t.tooltip.html+='{{#if {{namespace[catalog]}}rsid}}
See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t.fields.push("{{namespace[catalog]}}rsid","{{namespace[catalog]}}trait","{{namespace[catalog]}}log_pvalue"),t}(),me={namespace:{phewas:"phewas"},id:"phewaspvalues",type:"category_scatter",tag:"phewas",point_shape:"circle",point_size:70,tooltip_positioning:"vertical",id_field:"{{namespace[phewas]}}id",fields:["{{namespace[phewas]}}id","{{namespace[phewas]}}log_pvalue","{{namespace[phewas]}}trait_group","{{namespace[phewas]}}trait_label"],x_axis:{field:"{{namespace[phewas]}}x",category_field:"{{namespace[phewas]}}trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"{{namespace[phewas]}}log_pvalue",floor:0,upper_buffer:.15},color:[{field:"{{namespace[phewas]}}trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:["Trait: {{{{namespace[phewas]}}trait_label|htmlescape}}
","Trait Category: {{{{namespace[phewas]}}trait_group|htmlescape}}
","P-value: {{{{namespace[phewas]}}log_pvalue|logtoscinotation|htmlescape}}
"].join("")},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{{{namespace[phewas]}}trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"{{namespace[phewas]}}log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},fe={namespace:{gene:"gene",constraint:"constraint"},id:"genes",type:"genes",tag:"genes",fields:["{{namespace[gene]}}all","{{namespace[constraint]}}all"],id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:J(he)},be=Z({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},J(fe)),xe={namespace:{assoc:"assoc",catalog:"catalog"},id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"{{namespace[assoc]}}variant",x_axis:{field:"{{namespace[assoc]}}position"},color:"#0000CC",fields:["{{namespace[assoc]}}variant","{{namespace[assoc]}}chromosome","{{namespace[assoc]}}position","{{namespace[catalog]}}variant","{{namespace[catalog]}}rsid","{{namespace[catalog]}}trait","{{namespace[catalog]}}log_pvalue","{{namespace[catalog]}}pos"],filters:[{field:"{{namespace[catalog]}}rsid",operator:"!=",value:null},{field:"{{namespace[catalog]}}log_pvalue",operator:">",value:oe}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:J(ce),tooltip_positioning:"top"},ve={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},we={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},ze={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},$e={widgets:[{type:"title",title:"LocusZoom",subtitle:'v0.13.4',position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},ke=function(){const t=J($e);return t.widgets.push(J(ve)),t}(),Ee=function(){const t=J($e);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),Me={id:"association",tag:"association",min_height:200,height:225,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=J(ze);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:28},y2:{label:"Recombination Rate (cM/Mb)",label_offset:40}},legend:{orientation:"vertical",origin:{x:55,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[J(ue),J(pe),J(_e)]},Se={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",toolbar:J(ze),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:28,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[J(ge)]},Ne=function(){let t=J(Me);return t=Z({id:"associationcatalog",namespace:{assoc:"assoc",ld:"ld",catalog:"catalog"}},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{{{namespace[catalog]}}trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"{{namespace[catalog]}}trait",operator:"!=",value:null},{field:"{{namespace[catalog]}}log_pvalue",operator:">",value:oe},{field:"{{namespace[ld]}}state",operator:">",value:.4}],style:{"font-size":"10px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[J(ue),J(pe),J(ye)],t}(),Ae={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:50,bottom:20,left:50},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=J(ze);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},J(we)),t}(),data_layers:[J(be)]},Te={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:50,bottom:120,left:50},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:28}},data_layers:[J(ue),J(me)]},Le={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:50,bottom:10,left:50},inner_border:"rgb(210, 210, 210)",toolbar:J(ze),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[J(xe)]},Oe={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:ke,panels:[J(Me),J(Ae)]},Ce={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:ke,panels:[Le,Ne,Ae]},Pe={width:800,responsive_resize:!0,toolbar:$e,panels:[J(Te),Z({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"}}},J(Ae))],mouse_guide:!1},Re={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:J($e),panels:[J(Se),function(){const t=Object.assign({height:270},J(Ae)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},De={standard_association:re,standard_association_with_label:le,standard_genes:he,catalog_variant:ce,coaccessibility:de},Ie={ldlz2_pop_selector:ve,gene_selector_menu:we},je={standard_panel:ze,standard_plot:$e,standard_association:ke,region_nav_plot:Ee},Be={significance:ue,recomb_rate:pe,association_pvalues:_e,coaccessibility:ge,association_pvalues_catalog:ye,phewas_pvalues:me,genes:fe,genes_filtered:be,annotation_catalog:xe},Ue={association:Me,coaccessibility:Se,association_catalog:Ne,genes:Ae,phewas:Te,annotation_catalog:Le},Fe={standard_association:Oe,association_catalog:Ce,standard_phewas:Pe,coaccessibility:Re};const qe=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);if(i=Z(s,i),i.unnamespaced)return delete i.unnamespaced,J(i);let a="";"string"==typeof i.namespace?a=i.namespace:"object"==typeof i.namespace&&Object.keys(i.namespace).length&&(a=void 0!==i.namespace.default?i.namespace.default:i.namespace[Object.keys(i.namespace)[0]].toString()),a+=a.length?":":"";return J(H(i,i.namespace,a))}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=J(s);return super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return Z(t,e)}renameField(){return V(...arguments)}mutate_attrs(){return Y(...arguments)}query_attrs(){return W(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))qe.add(t,s,i);const Ge=qe;const He={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return k.select(t).html(""),k.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!k.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Mt(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Nt(s[2]),e=Nt(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Nt(s[2]),end:Nt(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Nt(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=k.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(et,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||$}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:$,DataLayers:ne,Layouts:Ge,MatchFunctions:Lt,ScaleFunctions:Bt,TransformationFunctions:P,Widgets:xt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),$}},Ze=[];He.use=function(t,...e){if(!Ze.includes(t)){if(e.unshift(He),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}Ze.push(t)}};const Je=He})(),LocusZoom=e.default})(); +/*! Locuszoom 0.14.0-beta.1 */ +var LocusZoom;(()=>{var t={483:(t,e,s)=>{"use strict";const i=s(919);t.exports=function(t,...e){if(!t){if(1===e.length&&e[0]instanceof Error)throw e[0];throw new i(e)}}},919:(t,e,s)=>{"use strict";const i=s(820);t.exports=class extends Error{constructor(t){super(t.filter((t=>""!==t)).map((t=>"string"==typeof t?t:t instanceof Error?t.message:i(t))).join(" ")||"Unknown error"),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e.assert)}}},820:t=>{"use strict";t.exports=function(...t){try{return JSON.stringify.apply(null,t)}catch(t){return"[Cannot display object: "+t.message+"]"}}},681:(t,e,s)=>{"use strict";const i=s(483),a={};e.B=class{constructor(){this._items=[],this.nodes=[]}add(t,e){const s=[].concat((e=e||{}).before||[]),a=[].concat(e.after||[]),o=e.group||"?",n=e.sort||0;i(!s.includes(o),`Item cannot come before itself: ${o}`),i(!s.includes("?"),"Item cannot come before unassociated items"),i(!a.includes(o),`Item cannot come after itself: ${o}`),i(!a.includes("?"),"Item cannot come after unassociated items"),Array.isArray(t)||(t=[t]);for(const e of t){const t={seq:this._items.length,sort:n,before:s,after:a,group:o,node:e};this._items.push(t)}if(!e.manual){const t=this._sort();i(t,"item","?"!==o?`added into group ${o}`:"","created a dependencies error")}return this.nodes}merge(t){Array.isArray(t)||(t=[t]);for(const e of t)if(e)for(const t of e._items)this._items.push(Object.assign({},t));this._items.sort(a.mergeSort);for(let t=0;tt.sort===e.sort?0:t.sort{function e(t){if("string"==typeof t.source.flags)return t.source.flags;var e=[];return t.global&&e.push("g"),t.ignoreCase&&e.push("i"),t.multiline&&e.push("m"),t.sticky&&e.push("y"),t.unicode&&e.push("u"),e.join("")}t.exports=function t(s){if("function"==typeof s)return s;var i=Array.isArray(s)?[]:{};for(var a in s){var o=s[a],n={}.toString.call(o).slice(8,-1);i[a]="Array"==n||"Object"==n?t(o):"Date"==n?new Date(o.getTime()):"RegExp"==n?RegExp(o.source,e(o)):o}return i}}},e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={exports:{}};return t[i](a,a.exports,s),a.exports}s.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return s.d(e,{a:e}),e},s.d=(t,e)=>{for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),s.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";s.d(i,{default:()=>ds});var t={};s.r(t),s.d(t,{AssociationLZ:()=>M,BaseAdapter:()=>$,BaseApiAdapter:()=>z,BaseLZAdapter:()=>k,BaseUMAdapter:()=>E,GeneConstraintLZ:()=>A,GeneLZ:()=>N,GwasCatalogLZ:()=>S,LDServer:()=>O,PheWASLZ:()=>j,RecombLZ:()=>T,StaticSource:()=>L});var e={};s.r(e),s.d(e,{htmlescape:()=>F,is_numeric:()=>H,log10:()=>C,logtoscinotation:()=>U,neglog10:()=>B,scinotation:()=>q,urlencode:()=>G});var a={};s.r(a),s.d(a,{BaseWidget:()=>yt,_Button:()=>ft,display_options:()=>Ot,download:()=>vt,download_png:()=>wt,filter_field:()=>xt,menu:()=>St,move_panel_down:()=>kt,move_panel_up:()=>zt,region_scale:()=>bt,remove_panel:()=>$t,resize_to_data:()=>Nt,set_state:()=>Tt,shift_region:()=>Et,title:()=>mt,toggle_legend:()=>At,zoom_region:()=>Mt});var o={};s.r(o),s.d(o,{categorical_bin:()=>Vt,effect_direction:()=>Qt,if_value:()=>Zt,interpolate:()=>Xt,numerical_bin:()=>Kt,ordinal_cycle:()=>Wt,stable_choice:()=>Yt});var n={};s.r(n),s.d(n,{BaseDataLayer:()=>ie,annotation_track:()=>oe,arcs:()=>he,category_scatter:()=>me,genes:()=>de,highlight_regions:()=>re,line:()=>_e,orthogonal_line:()=>ge,scatter:()=>fe});var r={};s.r(r),s.d(r,{data_layer:()=>es,panel:()=>ss,plot:()=>is,toolbar:()=>ts,toolbar_widgets:()=>Qe,tooltip:()=>Xe});const l="0.14.0-beta.1";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}class d{constructor(t,e,s={},i=null,a=null){this.key=t,this.value=e,this.metadata=s,this.prev=i,this.next=a}}class u{constructor(t=3){if(this._max_size=t,this._cur_size=0,this._store=new Map,this._head=null,this._tail=null,null===t||t<0)throw new Error('Cache "max_size" must be >= 0')}has(t){return this._store.has(t)}get(t){const e=this._store.get(t);return e?(this._head!==e&&this.add(t,e.value),e.value):null}add(t,e,s={}){if(0===this._max_size)return;const i=this._store.get(t);i&&this._remove(i);const a=new d(t,e,s,null,this._head);if(this._head?this._head.prev=a:this._tail=a,this._head=a,this._store.set(t,a),this._max_size>=0&&this._cur_size>=this._max_size){const t=this._tail;this._tail=this._tail.prev,this._remove(t)}this._cur_size+=1}clear(){this._head=null,this._tail=null,this._cur_size=0,this._store=new Map}remove(t){const e=this._store.get(t);return!!e&&(this._remove(e),!0)}_remove(t){null!==t.prev?t.prev.next=t.next:this._head=t.next,null!==t.next?t.next.prev=t.prev:this._tail=t.prev,this._store.delete(t.key),this._cur_size-=1}find(t){let e=this._head;for(;e;){const s=e.next;if(t(e))return e;e=s}}}var _=s(957),p=s.n(_);function g(t){return"object"!=typeof t?t:p()(t)}var y=s(681);function f(t,e,s,i=!0){if(!s.length)return[];const a=s.map((t=>function(t){const e=/^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(t);if(!e)throw new Error(`Unable to parse dependency specification: ${t}`);let{name_alone:s,name_deps:i,deps:a}=e.groups;return s?[s,[]]:(a=a.split(/\s*,\s*/),[i,a])}(t))),o=new Map(a),n=new y.B;for(let[t,e]of o.entries())try{n.add(t,{after:e,group:t})}catch(e){throw new Error(`Invalid or possible circular dependency specification for: ${t}`)}const r=n.nodes,l=new Map;for(let s of r){const i=e.get(s);if(!i)throw new Error(`Data has been requested from source '${s}', but no matching source was provided`);const a=o.get(s)||[],n=Promise.all(a.map((t=>l.get(t)))).then((e=>{const a=Object.assign({_provider_name:s},t);return i.getData(a,...e)}));l.set(s,n)}return Promise.all([...l.values()]).then((t=>i?t[t.length-1]:t))}function m(t,e){const s=new Map;for(let i of t){const t=i[e];if(void 0===t)throw new Error(`All records must specify a value for the field "${e}"`);if("object"==typeof t)throw new Error("Attempted to group on a field with non-primitive values");let a=s.get(t);a||(a=[],s.set(t,a)),a.push(i)}return s}function b(t,e,s,i,a){const o=m(s,a),n=[];for(let s of e){const e=s[i],a=o.get(e)||[];a.length?n.push(...a.map((t=>Object.assign({},g(t),g(s))))):"inner"!==t&&n.push(g(s))}if("outer"===t){const t=m(e,i);for(let e of s){const s=e[a];(t.get(s)||[]).length||n.push(g(e))}}return n}function x(t,e,s,i){return b("left",...arguments)}const v=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function w(t,e=!1){const s=t&&t.match(v);if(s)return s.slice(1);if(e)return null;throw new Error(`Could not understand marker format for ${t}. Should be of format chr:pos or chr:pos_ref/alt`)}class ${constructor(){throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.')}}class z extends ${}class k extends class extends class{constructor(t={}){this._config=t;const{cache_enabled:e=!0,cache_size:s=3}=t;this._enable_cache=e,this._cache=new u(s)}_buildRequestOptions(t,e){return Object.assign({},t)}_getCacheKey(t){if(this._enable_cache)throw new Error("Method not implemented");return null}_performRequest(t){throw new Error("Not implemented")}_normalizeResponse(t,e){return t}_annotateRecords(t,e){return t}_postProcessResponse(t,e){return t}getData(t={},...e){t=this._buildRequestOptions(t,...e);const s=this._getCacheKey(t);let i;return this._enable_cache&&this._cache.has(s)?i=this._cache.get(s):(i=Promise.resolve(this._performRequest(t)).then((e=>this._normalizeResponse(e,t))),this._cache.add(s,i,t._cache_meta),i.catch((t=>this._cache.remove(s)))),i.then((t=>g(t))).then((e=>this._annotateRecords(e,t))).then((e=>this._postProcessResponse(e,t)))}}{constructor(t={}){super(t),this._url=t.url}_getCacheKey(t){return this._getURL(t)}_getURL(t){return this._url}_performRequest(t){const e=this._getURL(t);if(!this._url)throw new Error('Web based resources must specify a resource URL as option "url"');return fetch(e).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}_normalizeResponse(t,e){return"string"==typeof t?JSON.parse(t):t}}{constructor(t={}){t.params&&(console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'),Object.assign(t,t.params||{}),delete t.params),super(t);const{prefix_namespace:e=!0,limit_fields:s}=t;this._prefix_namespace=e,this._limit_fields=!!s&&new Set(s)}_getCacheKey(t){let{chr:e,start:s,end:i}=t;const a=this._cache.find((({metadata:t})=>e===t.chr&&s>=t.start&&i<=t.end));return a&&({chr:e,start:s,end:i}=a.metadata),t._cache_meta={chr:e,start:s,end:i},`${e}_${s}_${i}`}_postProcessResponse(t,e){if(!this._prefix_namespace||!Array.isArray(t))return t;const{_limit_fields:s}=this,{_provider_name:i}=e;return t.map((t=>Object.entries(t).reduce(((t,[e,a])=>(s&&!s.has(e)||(t[`${i}:${e}`]=a),t)),{})))}_findPrefixedKey(t,e){const s=new RegExp(`:${e}$`),i=Object.keys(t).find((t=>s.test(t)));if(!i)throw new Error(`Could not locate the required key name: ${e} in dependent data`);return i}}class E extends k{constructor(t={}){super(t),this._genome_build=t.genome_build||t.build}_validateBuildSource(t,e){if(t&&e||!t&&!e)throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`)}_normalizeResponse(t,e){let s=super._normalizeResponse(...arguments);if(s=s.data||s,Array.isArray(s))return s;const i=Object.keys(s),a=s[i[0]].length;if(!i.every((function(t){return s[t].length===a})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const o=[],n=Object.keys(s);for(let t=0;t25||"GRCh38"===s)return Promise.resolve([]);e=`{${e.join(" ")} }`;const i=this._getURL(t),a=JSON.stringify({query:e});return fetch(i,{method:"POST",body:a,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}_normalizeResponse(t){if("string"!=typeof t)return t;return JSON.parse(t).data}}class O extends E{constructor(t){t.limit_fields||(t.limit_fields=["variant2","position2","correlation"]),super(t)}__find_ld_refvar(t,e){const s=this._findPrefixedKey(e[0],"variant"),i=this._findPrefixedKey(e[0],"log_pvalue");let a,o={};if(t.ldrefvar)a=t.ldrefvar,o=e.find((t=>t[s]===a))||{};else{let t=0;for(let n of e){const{[s]:e,[i]:r}=n;r>t&&(t=r,a=e,o=n)}}o.lz_is_ld_refvar=!0;const n=w(a,!0);if(!n)throw new Error("Could not request LD for a missing or incomplete marker format");const[r,l,h,c]=n;a=`${r}:${l}`,h&&c&&(a+=`_${h}/${c}`);const d=+l;return d&&t.ldrefvar&&t.chr&&(r!==t.chr||dt.end)?(t.ldrefvar=null,this.__find_ld_refvar(t,e)):a}_buildRequestOptions(t,e){if(!e)throw new Error("LD request must depend on association data");const s=super._buildRequestOptions(...arguments);if(!e.length)return s._skip_request=!0,s;s.ld_refvar=this.__find_ld_refvar(t,e);const i=t.genome_build||this._config.build||"GRCh37";let a=t.ld_source||this._config.source||"1000G";const o=t.ld_pop||this._config.population||"ALL";return"1000G"===a&&"GRCh38"===i&&(a="1000G-FRZ09"),this._validateBuildSource(i,null),Object.assign({},s,{genome_build:i,ld_source:a,ld_population:o})}_getURL(t){const e=this._config.method||"rsquare",{chr:s,start:i,end:a,ld_refvar:o,genome_build:n,ld_source:r,ld_population:l}=t;return[super._getURL(t),"genome_builds/",n,"/references/",r,"/populations/",l,"/variants","?correlation=",e,"&variant=",encodeURIComponent(o),"&chrom=",encodeURIComponent(s),"&start=",encodeURIComponent(i),"&stop=",encodeURIComponent(a)].join("")}_getCacheKey(t){const e=super._getCacheKey(t),{ld_refvar:s,ld_source:i,ld_population:a}=t;return`${e}_${s}_${i}_${a}`}_performRequest(t){if(t._skip_request)return Promise.resolve([]);const e=this._getURL(t);let s={data:{}},i=function(t){return fetch(t).then().then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){s.data[e]=(s.data[e]||[]).concat(t.data[e])})),t.next?i(t.next):s}))};return i(e)}}class T extends E{constructor(t){t.limit_fields||(t.limit_fields=["position","recomb_rate"]),super(t)}_getURL(t){const e=t.genome_build||this._config.build;let s=this._config.source;this._validateBuildSource(e,s);const i=e?`&build=${e}`:` and id in ${s}`;return`${super._getURL(t)}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${i}`}}class L extends k{constructor(t={}){super(...arguments);const{data:e}=t;if(!e||Array.isArray(t))throw new Error("'StaticSource' must provide data as required option 'config.data'");this._data=e}_performRequest(t){return Promise.resolve(this._data)}}class j extends E{_getURL(t){const e=(t.genome_build?[t.genome_build]:null)||this._config.build;if(!e||!Array.isArray(e)||!e.length)throw new Error(["Adapter",this.constructor.name,"requires that you specify array of one or more desired genome build names"].join(" "));return[super._getURL(t),"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",e.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}_getCacheKey(t){return this._getURL(t)}}const P=new c;for(let[e,s]of Object.entries(t))P.add(e,s);P.add("StaticJSON",L),P.add("LDLZ2",O);const R=P,I=d3,D={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function C(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function B(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function U(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function q(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function F(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function H(t){return"number"==typeof t}function G(t){return encodeURIComponent(t)}const J=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,s]of Object.entries(e))J.add(t,s);const Z=J;class K{constructor(t){if(!/^(?:\w+:\w+|^\w+)(?:\|\w+)*$/.test(t))throw new Error(`Invalid field specifier: '${t}'`);const[e,...s]=t.split("|");this.full_name=t,this.field_name=e,this.transformations=s.map((t=>Z.get(t)))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[this.field_name]?s=t[this.field_name]:e&&void 0!==e[this.field_name]&&(s=e[this.field_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const V=/^(\*|[\w]+)/,W=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function Y(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=V.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=V.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=W.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function X(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function Q(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const o=t[s.attr];1===e.length?void 0!==o&&a.push([s.attr]):a.push(...Q(o,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...Q(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...Q(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[o,n]of Object.entries(t))a.push(...Q(n,e).map((t=>[o].concat(t)))),"*"===s.attr&&a.push(...Q(n,i).map((t=>[o].concat(t))))}}else if(s.attrs)for(let[e,o]of Object.entries(t)){const[t,n,r]=X(o,s.attrs);r===s.value&&a.push(...Q(o,i).map((t=>[e].concat(t))))}const o=(n=a,r=JSON.stringify,[...new Map(n.map((t=>[r(t),t]))).values()]);var n,r;return o.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),o}function tt(t,e){const s=function(t,e){let s=[];for(let i of Q(t,e))s.push(X(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=Y(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const et=Math.sqrt(3),st={draw(t,e){const s=-Math.sqrt(e/(3*et));t.moveTo(0,2*-s),t.lineTo(-et*s,s),t.lineTo(et*s,s),t.closePath()}};function it(t,e){if(e=e||{},!t||"object"!=typeof t||"object"!=typeof e)throw new Error("Layout and shared namespaces must be provided as objects");for(let[s,i]of Object.entries(t))"namespace"===s?Object.keys(i).forEach((t=>{const s=e[t];s&&(i[t]=s)})):null!==i&&"object"==typeof i&&(t[s]=it(i,e));return t}function at(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=at(t[s],e[s])):t[s]=ot(e[s])}return t}function ot(t){return JSON.parse(JSON.stringify(t))}function nt(t){if(!t)return null;if("triangledown"===t)return st;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return I[e]||null}function rt(t,e,s=null){const i=new Set;if(!s){if(!e.length)return i;const t=e.join("|");s=new RegExp(`(?:{{)?(?:#if *)?((?:${t}):\\w+)`,"g")}for(const a of Object.values(t)){const t=typeof a;let o=[];if("string"===t){let t;for(;null!==(t=s.exec(a));)o.push(t[1])}else{if(null===a||"object"!==t)continue;o=rt(a,e,s)}for(let t of o)i.add(t)}return i}function lt(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>lt(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,o)=>(a[o]=lt(t[o],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const o=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(o,s)}}function ht(t,e,s){return function(t,e,s){return tt(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function ct(t,e){return function(t,e){return tt(t,e).map((t=>t[2]))}(t,e)}class dt{constructor(t,e,s){this._callable=ls.get(t),this._initiator=e,this._params=s||[]}getData(t,...e){const s={plot_state:t,data_layer:this._initiator};return Promise.resolve(this._callable(s,e,...this._params))}}const ut=class{constructor(t){this._sources=t}config_to_sources(t={},e=[],s){const i=new Map,a=Object.keys(t);let o=e.find((t=>"fetch"===t.type));o||(o={type:"fetch",from:a},e.unshift(o));const n=/^\w+$/;for(let[e,s]of Object.entries(t)){if(!n.test(e))throw new Error(`Invalid namespace name: '${e}'. Must contain only alphanumeric characters`);const t=this._sources.get(s);if(!t)throw new Error(`A data layer has requested an item not found in DataSources: data type '${e}' from ${s}`);i.set(e,t),o.from.find((t=>t.split("(")[0]===e))||o.from.push(e)}let r=Array.from(o.from);for(let t of e){let{type:e,name:a,requires:o,params:n}=t;if("fetch"!==e){let l=0;if(a||(a=t.name=`join${l}`,l+=1),i.has(a))throw new Error(`Configuration error: within a layer, join name '${a}' must be unique`);o.forEach((t=>{if(!i.has(t))throw new Error(`Data operation cannot operate on unknown provider '${t}'`)}));const h=new dt(e,s,n);i.set(a,h),r.push(`${a}(${o.join(", ")})`)}}return[i,r]}getData(t,e,s){return s.length?f(t,e,s,!0):Promise.resolve([])}};function _t(){return{showing:!1,selector:null,content_selector:null,hide_delay:null,show:(t,e)=>(this.curtain.showing||(this.curtain.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&>(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function pt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function gt(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class yt{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&>(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class ft{constructor(t){if(!(t instanceof yt))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),o=this.menu.outer_selector.node().getBoundingClientRect(),n=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-o.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-o.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),_=Math.min(n+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${_}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(gt,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class mt extends yt{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class bt extends yt{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(qt(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&>(this.selector,this.layout.style),this}}class xt extends yt{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class vt extends yt{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=I.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+I.select(this).attr("dy").substring(-2).slice(0,-2);I.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class wt extends vt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((o,n)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{o(URL.createObjectURL(t))}))},r.src=t}))}))}}class $t extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),I.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),I.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class zt extends yt{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class kt extends yt{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot._panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class Et extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${qt(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class Mt extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class St extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class Nt extends yt{constructor(t){super(...arguments)}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class At extends yt{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new ft(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class Ot extends yt{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,o={};s.forEach((t=>{const e=a[t];void 0!==e&&(o[t]=ot(e))})),this._selected_item="default",this.button=new ft(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,n=(a,n,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==n[t];i.layout[t]=e?n[t]:o[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return n(r,o,"default"),a.options.forEach(((t,e)=>n(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class Tt extends yt{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new ft(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,o)=>{const n=s.append("tr"),r=`${e}${o}`;n.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",o).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),n.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const Lt=new c;for(let[t,e]of Object.entries(a))Lt.add(t,e);const jt=Lt;class Pt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{this.addWidget(t)})),"panel"===this.type&&I.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}addWidget(t){try{const e=jt.create(t.type,t,this);return this.widgets.push(e),e}catch(t){console.warn("Failed to create widget"),console.error(t)}}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot._panel_boundaries.dragging||this.parent_plot._interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=I.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=I.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const Rt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:12,hidden:!1};class It{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=at(this.parent.layout.legend||{},Rt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((a=>{Array.isArray(this.parent.data_layers[a].layout.legend)&&this.parent.data_layers[a].layout.legend.forEach((a=>{const o=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),n=+a.label_size||+this.layout.label_size||12;let r=0,l=n/2+t/2;i=Math.max(i,n+t);const h=a.shape||"",c=nt(h);if("line"===h){const e=+a.length||16,s=n/4+t/2;o.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(gt,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;o.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(gt,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if(c){const e=+a.size||40,s=Math.ceil(Math.sqrt(e/Math.PI));o.append("path").attr("class",a.class||"").attr("d",I.symbol().size(e).type(c)).attr("transform",`translate(${s}, ${s+t/2})`).attr("fill",a.color||{}).call(gt,a.style||{}),r=2*s+t,l=Math.max(2*s+t/2,l),i=Math.max(i,2*s+t)}o.append("text").attr("text-anchor","left").attr("class","lz-label").attr("x",r).attr("y",l).style("font-size",n).text(a.label);const d=o.node().getBoundingClientRect();if("vertical"===this.layout.orientation)s+=d.height+t,i=0;else{const a=this.layout.origin.x+e+d.width;e>t&&a>this.parent.parent.layout.width&&(s+=i,e=t,o.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(o)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const Dt={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class Ct{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"!=typeof t.id||!t.id)throw new Error('Panel layouts must specify "id"');if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`);this.id=t.id,this._initialized=!1,this._layout_idx=null,this.svg={},this.layout=at(t||{},Dt),this.parent?(this.state=this.parent.state,this._state_id=this.id,this.state[this._state_id]=this.state[this._state_id]||{}):(this.state=null,this._state_id=null),this.data_layers={},this._data_layer_ids_by_z_index=[],this._data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this._zoom_timeout=null,this._event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this._event_hooks[t]&&this._event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=at(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(gt,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id '${t.id}'; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=xe.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this._data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this._data_layer_ids_by_z_index.length+e.layout.z_index,0)),this._data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this._data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id]._layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){const e=this.data_layers[t];if(!e)throw new Error(`Unable to remove data layer, ID not found: ${t}`);return e.destroyAllTooltips(),e.svg.container&&e.svg.container.remove(),this.layout.data_layers.splice(e._layout_idx,1),delete this.state[e._state_id],delete this.data_layers[t],this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id]._layout_idx=e})),this}clearSelections(){return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height);const{cliparea:t}=this.layout,{margin:e}=this.layout;this.inner_border.attr("x",e.left).attr("y",e.top).attr("width",this.parent_plot.layout.width-(e.left+e.right)).attr("height",this.layout.height-(e.top+e.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const s=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),o=Math.pow(10,e);return t===1/0&&(t=o),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,o),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},i={},a=this.layout.axes;if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};a.x.range&&(t.start=a.x.range.start||t.start,t.end=a.x.range.end||t.end),i.x=[t.start,t.end],i.x_shifted=[t.start,t.end]}if(this.y1_extent){const e={start:t.height,end:0};a.y1.range&&(e.start=a.y1.range.start||e.start,e.end=a.y1.range.end||e.end),i.y1=[e.start,e.end],i.y1_shifted=[e.start,e.end]}if(this.y2_extent){const e={start:t.height,end:0};a.y2.range&&(e.start=a.y2.range.start||e.start,e.end=a.y2.range.end||e.end),i.y2=[e.start,e.end],i.y2_shifted=[e.start,e.end]}let{_interaction:o}=this.parent;const n=o.dragging;if(o.panel_id&&(o.panel_id===this.id||o.linked_panel_ids.includes(this.id))){let a,r=null;if(o.zooming&&"function"==typeof this.x_scale){const s=Math.abs(this.x_extent[1]-this.x_extent[0]),n=Math.round(this.x_scale.invert(i.x_shifted[1]))-Math.round(this.x_scale.invert(i.x_shifted[0]));let r=o.zooming.scale;const l=Math.floor(n*(1/r));r<1&&!isNaN(this.parent.layout.max_region_scale)?r=1/(Math.min(l,this.parent.layout.max_region_scale)/n):r>1&&!isNaN(this.parent.layout.min_region_scale)&&(r=1/(Math.max(l,this.parent.layout.min_region_scale)/n));const h=Math.floor(s*r);a=o.zooming.center-e.left-this.layout.origin.x;const c=a/t.width,d=Math.max(Math.floor(this.x_scale.invert(i.x_shifted[0])-(h-n)*c),1);i.x_shifted=[this.x_scale(d),this.x_scale(d+h)]}else if(n)switch(n.method){case"background":i.x_shifted[0]=+n.dragged_x,i.x_shifted[1]=t.width+n.dragged_x;break;case"x_tick":I.event&&I.event.shiftKey?(i.x_shifted[0]=+n.dragged_x,i.x_shifted[1]=t.width+n.dragged_x):(a=n.start_x-e.left-this.layout.origin.x,r=s(a/(a+n.dragged_x),3),i.x_shifted[0]=0,i.x_shifted[1]=Math.max(t.width*(1/r),1));break;case"y1_tick":case"y2_tick":{const o=`y${n.method[1]}_shifted`;I.event&&I.event.shiftKey?(i[o][0]=t.height+n.dragged_y,i[o][1]=+n.dragged_y):(a=t.height-(n.start_y-e.top-this.layout.origin.y),r=s(a/(a-n.dragged_y),3),i[o][0]=t.height,i[o][1]=t.height-t.height*(1/r))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(i[t][0]),this[`${t}_scale`].invert(i[t][1])],this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!I.event.shiftKey&&!I.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(I.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=I.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,I.event.wheelDelta||-I.event.detail||-I.event.deltaY));0!==e&&(this.parent._interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),o=this.parent._interaction,o.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this._zoom_timeout&&clearTimeout(this._zoom_timeout),this._zoom_timeout=setTimeout((()=>{this.parent._interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this._initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{const e=this.layout.axes[t];Object.keys(e).length&&!1!==e.render?(e.render=!0,e.label=e.label||null):e.render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){const s=this.layout;return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),s.height=Math.max(Math.round(+e),s.min_height)),s.cliparea.width=Math.max(this.parent_plot.layout.width-(s.margin.left+s.margin.right),0),s.cliparea.height=Math.max(s.height-(s.margin.top+s.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",s.height),this._initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this._initialized&&this.render(),this}setMargin(t,e,s,i){let a;const{cliparea:o,margin:n}=this.layout;return!isNaN(t)&&t>=0&&(n.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(n.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(n.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(n.left=Math.max(Math.round(+i),0)),n.top+n.bottom>this.layout.height&&(a=Math.floor((n.top+n.bottom-this.layout.height)/2),n.top-=a,n.bottom-=a),n.left+n.right>this.parent_plot.layout.width&&(a=Math.floor((n.left+n.right-this.parent_plot.layout.width)/2),n.left-=a,n.right-=a),["top","right","bottom","left"].forEach((t=>{n[t]=Math.max(n[t],0)})),o.width=Math.max(this.parent_plot.layout.width-(n.left+n.right),0),o.height=Math.max(this.layout.height-(n.top+n.bottom),0),o.origin.x=n.left,o.origin.y=n.top,this._initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=_t.call(this),this.loader=pt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new Pt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new It(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this._data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(I.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent._panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){const{parent:t}=this,e=this.layout.y_index;return t._panel_ids_by_y_index[e-1]&&(t._panel_ids_by_y_index[e]=t._panel_ids_by_y_index[e-1],t._panel_ids_by_y_index[e-1]=this.id,t.applyPanelYIndexesToPanelLayouts(),t.positionPanels()),this}moveDown(){const{_panel_ids_by_y_index:t}=this.parent;return t[this.layout.y_index+1]&&(t[this.layout.y_index]=t[this.layout.y_index+1],t[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this._data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this._data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this._data_promises).then((()=>{this._initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=I.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=I.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this._data_layer_ids_by_z_index.reduce(((e,a)=>{const o=s.data_layers[a];return e.concat(o.getTicks(t,i))}),[]).map((t=>{let s={};return s=at(s,e),at(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,o=1.5,n=.5+1.5*o,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(_=parseFloat(_.toFixed(c)));u.push(_),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eqt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=I.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=I.select(this).select("text");s[`${t}_ticks`][i].style&>(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const o=this.layout.axes[t].label||null;return null!==o&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(Ht(o,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof I.select(this).node().focus&&I.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";I.event&&I.event.shiftKey&&(i="move"),I.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){I.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this._data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this._data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}D.verbs.forEach(((t,e)=>{const s=D.adjectives[e],i=`un${t}`;Ct.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Ct.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Bt={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Ut{constructor(t,e,s){this._initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this._panel_ids_by_y_index=[],this._remap_promises=[],this.layout=s,at(this.layout,Bt),this._base_layout=ot(this.layout),this.state=this.layout.state,this.lzd=new ut(e),this._external_listeners=new Map,this._event_hooks={},this._interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this._event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this._event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Ct(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this._panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this._panel_ids_by_y_index.length+e.layout.y_index,0)),this._panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this._panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id]._layout_idx=s,this._initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t]._data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i._layer_state,delete this.layout.state[i._state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){const e=this.panels[t];if(!e)throw new Error(`Unable to remove panel, ID not found: ${t}`);return this._panel_boundaries.hide(),this.clearPanelData(t),e.loader.hide(),e.toolbar.destroy(!0),e.curtain.hide(),e.svg.container&&e.svg.container.remove(),this.layout.panels.splice(e._layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id]._layout_idx=e})),this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this._initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e){const{from_layer:s,namespace:i,data_operations:a,onerror:o}=t,n=o||function(t){console.error("An error occurred while acting on an external callback",t)};if(s){const t=`${this.getBaseId()}.`,i=s.startsWith(t)?s:`${t}${s}`;let a=!1;for(let t of Object.values(this.panels))if(a=Object.values(t.data_layers).some((t=>t.getBaseId()===i)),a)break;if(!a)throw new Error(`Could not subscribe to unknown data layer ${i}`);const o=t=>{if(t.data.layer===i)try{e(t.data.content,this)}catch(t){n(t)}};return this.on("data_from_layer",o),o}if(!i)throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option");const[r,l]=this.lzd.config_to_sources(i,a),h=()=>{try{this.lzd.getData(this.state,r,l).then((t=>e(t,this))).catch(n)}catch(t){n(t)}};return this.on("data_rendered",h),h}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this._remap_promises=[],this.loading_data=!0;for(let t in this.panels)this._remap_promises.push(this.panels[t].reMap());return Promise.all(this._remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e._data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this._initialized=!1,this.svg=null,this.panels=null}mutateLayout(){Object.values(this.panels).forEach((t=>{Object.values(t.data_layers).forEach((t=>t.mutateLayout()))}))}_canInteract(t){t=t||null;const{_interaction:e}=this;return t?(void 0===e.panel_id||e.panel_id===t)&&!this.loading_data:!(e.dragging||e.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==I.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this._panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");return this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,o=e.layout.height*s;e.setDimensions(a,o),e.setOrigin(0,i),i+=o,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this._initialized&&(this._panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e of Object.values(this.panels))e.layout.interaction.x_linked&&(t.left=Math.max(t.left,e.layout.margin.left),t.right=Math.max(t.right,e.layout.margin.right));let e=0;return this._panel_ids_by_y_index.forEach((s=>{const i=this.panels[s],a=i.layout;if(i.setOrigin(0,e),e+=this.panels[s].layout.height,a.interaction.x_linked){const e=Math.max(t.left-a.margin.left,0)+Math.max(t.right-a.margin.right,0);a.width+=e,a.margin.left=t.left,a.margin.right=t.right,a.cliparea.origin.x=t.left}})),this.setDimensions(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize){I.select(this.container).classed("lz-container-responsive",!0);const t=()=>this.rescaleSVG();if(window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t),"undefined"!=typeof IntersectionObserver){const t={root:document.documentElement,threshold:.9};new IntersectionObserver(((t,e)=>{t.some((t=>t.intersectionRatio>0))&&this.rescaleSVG()}),t).observe(this.container)}const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}if(this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this._mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=_t.call(this),this.loader=pt.call(this),this._panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent._panel_ids_by_y_index.forEach(((t,e)=>{const s=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=I.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent._panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+I.event.dy);const i=t.layout.height-s;this.parent._panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent._panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent._panel_boundaries.selectors.push(s)}));const t=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=I.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+I.event.dx,this.parent._total_height+I.event.dy)})),t.call(e),this.parent._panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent._panel_ids_by_y_index[s]],a=i._getPageOrigin(),o=t.x,n=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${n}px`).style("left",`${o}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&I.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this._panel_boundaries.hide_timeout),this._panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this._panel_boundaries.hide_timeout=setTimeout((()=>{this._panel_boundaries.hide()}),300)})),this.toolbar=new Pt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this._mouse_guide.vertical.attr("x",-1),this._mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=I.mouse(this.svg.node());this._mouse_guide.vertical.attr("x",t[0]),this._mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{const{_interaction:t}=this;if(t.dragging){const e=I.mouse(this.svg.node());I.event&&I.event.preventDefault(),t.dragging.dragged_x=e[0]-t.dragging.start_x,t.dragging.dragged_y=e[1]-t.dragging.start_y,this.panels[t.panel_id].render(),t.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=I.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this._initialized=!0;const a=this.svg.node().getBoundingClientRect(),o=a.width?a.width:this.layout.width,n=a.height?a.height:this._total_height;return this.setDimensions(o,n),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof Ct&&s&&this._canInteract()))return this.stopDrag();const i=I.mouse(this.svg.node());return this._interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){const{_interaction:t}=this;if(!t.dragging)return this;if("object"!=typeof this.panels[t.panel_id])return this._interaction={},this;const e=this.panels[t.panel_id],s=(t,s,i)=>{e._data_layer_ids_by_z_index.forEach((a=>{const o=e.data_layers[a].layout[`${t}_axis`];o.axis===s&&(o.floor=i[0],o.ceiling=i[1],delete o.lower_buffer,delete o.upper_buffer,delete o.min_extent,delete o.ticks)}))};switch(t.dragging.method){case"background":case"x_tick":0!==t.dragging.dragged_x&&(s("x",1,e.x_extent),this.applyState({start:e.x_extent[0],end:e.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==t.dragging.dragged_y){const i=parseInt(t.dragging.method[1]);s("y",i,e[`y${i}_extent`])}}return this._interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function qt(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),o=Math.min(Math.max(e,0),2),n=Math.min(Math.max(a,o),12);let r=`${(t/Math.pow(10,e)).toFixed(n)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Ft(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function Ht(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const o=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(o())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},n=[];for(;i.length>0;)n.push(o());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new K(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return n.map(l).join("")}const Gt=new h;Gt.add("=",((t,e)=>t===e)),Gt.add("!=",((t,e)=>t!=e)),Gt.add("<",((t,e)=>tt<=e)),Gt.add(">",((t,e)=>t>e)),Gt.add(">=",((t,e)=>t>=e)),Gt.add("%",((t,e)=>t%e)),Gt.add("in",((t,e)=>e&&e.includes(t))),Gt.add("match",((t,e)=>t&&t.includes(e)));const Jt=Gt,Zt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Kt=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Wt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Yt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let o=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var o=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(o=i)})),null===o)return a;const t=(+e-s[o-1])/(s[o]-s[o-1]);return isFinite(t)?I.interpolate(i[o-1],i[o])(t):a}};function Qt(t,e){if(void 0===e)return null;const{beta_field:s,stderr_beta_field:i,"+":a=null,"-":o=null}=t;if(!s||!i)throw new Error("effect_direction must specify how to find required 'beta' and 'stderr_beta' fields");const n=e[s],r=e[i];if(void 0!==n)if(void 0!==r){if(n-2*r>0)return a;if(n+2*r<0)return o||null}else{if(n>0)return a;if(n<0)return o}return null}const te=new h;for(let[t,e]of Object.entries(o))te.add(t,e);te.add("if",Zt);const ee=te,se={id:"",type:"",tag:"custom_data_type",namespace:{},data_operations:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class ie{constructor(t,e){this._initialized=!1,this._layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=at(t||{},se),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=ot(this.layout),this.state={},this._state_id=null,this._layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this._tooltips={}),this._global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1},this._data_contract=new Set,this._entities=new Map,this._dependencies=[],this.mutateLayout()}render(){throw new Error("Method must be implemented")}moveForward(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e+1]&&(t[e]=t[e+1],t[e+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e-1]&&(t[e]=t[e-1],t[e-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this._layer_state.extra_fields[i]||(this._layer_state.extra_fields[i]={}),this._layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}mutateLayout(){if(this.parent_plot){const{namespace:t,data_operations:e}=this.layout;this._data_contract=rt(this.layout,Object.keys(t));const[s,i]=this.parent_plot.lzd.config_to_sources(t,e,this);this._entities=s,this._dependencies=i}}_getDataExtent(t,e){return t=t||this.data,I.extent(t,(t=>+new K(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field;let i=t[s];if(void 0===i&&/{{[^{}]*}}/.test(s)&&(i=Ht(s,t,{})),null==i)throw new Error("Unable to generate element ID");const a=i.toString().replace(/\W/g,""),o=`${this.getBaseId()}-${a}`.replace(/([:.[\],])/g,"_");return t[e]=o,o}getElementStatusNodeId(t){return null}getElementById(t){const e=I.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Jt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new K(t):null;if(this.data.length&&this._data_contract.size){const t=new Set(this._data_contract);for(let e of this.data)if(Object.keys(e).forEach((e=>t.delete(e))),!t.size)break;t.size&&console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...t]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`)}return this.data.forEach(((a,o)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(p+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=_<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-_,0),e=Math.max(c.width/2+_-u,0);y=h.x+_-c.width/2-e+t,b=h.x+_-y-7,"top"===w?(g=h.y+p-(v+c.height+8),f="down",m=c.height-1):(g=h.y+p+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+_+x+8,f="left",b=-8):(y=h.x+_-c.width-x-8,f="right",b=c.width-1),p-c.height/2<=0?(g=h.y+p-10.5-6,m=6):p+c.height/2>=d?(g=h.y+p+7+6-c.height,m=c.height-14-6):(g=h.y+p-c.height/2,m=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${f}`).style("left",`${b}px`).style("top",`${m}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:o}=t,n=Jt.get(i),r=this.getElementAnnotation(e);n(s?new K(s).resolve(e,r):e,o)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this._layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;D.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this._state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this._state_id]=t),this._layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this._tooltips[e])return this._tooltips[e]={data:t,arrow:null,selector:I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this._layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this._tooltips[e].selector.html(""),this._tooltips[e].arrow=null,this.layout.tooltip.html&&this._tooltips[e].selector.html(Ht(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this._tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this._tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this._tooltips[s]&&("object"==typeof this._tooltips[s].selector&&this._tooltips[s].selector.remove(),delete this._tooltips[s]),!e){this._layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this._tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this._tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this._tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this._tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){const s=this.layout.tooltip;if("object"!=typeof s)return this;const i=this.getElementId(t),a=(t,e,s)=>{let i=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",i=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let o;for(let n in e)o=a(t,e[n],n),null===i?i=o:"and"===s?i=i&&o:"or"===s&&(i=i||o)}}return i};let o={};"string"==typeof s.show?o={and:[s.show]}:"object"==typeof s.show&&(o=s.show);let n={};"string"==typeof s.hide?n={and:[s.hide]}:"object"==typeof s.hide&&(n=s.hide);const r=this._layer_state;var l={};D.adjectives.forEach((t=>{const e=`un${t}`;l[t]=r.status_flags[t].has(i),l[e]=!l[t]}));const h=a(l,o),c=a(l,n),d=r.status_flags.has_tooltip.has(i);return!h||!e&&!d||c?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),I.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const o=this.getElementStatusNodeId(e);null!==o&&I.select(`#${o}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const n=!this._layer_state.status_flags[t].has(a);s&&n&&this._layer_state.status_flags[t].add(a),s||n||this._layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,n),n&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!n&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!n&&s||this.parent.emit("match_requested",{value:new K(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!D.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this._layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this._layer_state.status_flags[t]=new Set}return this._global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||I.select(I.event.target).datum(),s===!!I.event.ctrlKey&&i===!!I.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a._layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=Ht(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this._layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e._state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this._entities,this._dependencies).then((t=>{this.data=t,this.applyDataMethods(),this._initialized=!0,this.parent.emit("data_from_layer",{layer:this.getBaseId(),content:ot(t)},!0)}))}}D.verbs.forEach(((t,e)=>{const s=D.adjectives[e],i=`un${t}`;ie.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},ie.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},ie.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},ie.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const ae={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class oe extends ie{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");at(t,ae),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],o=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+o)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const ne={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class re extends ie{constructor(t){if(at(t,ne),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions.length&&t.namespace&&Object.keys(t.namespace).length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>I.ascending(t[s],e[s])||I.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,o){const n=a[a.length-1]||t;if(t[s]===n[s]&&t[i]<=n[e]){const s=Math.min(n[i],t[i]),o=Math.max(n[e],t[e]);t=Object.assign({},n,t,{[i]:s,[e]:o}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const le={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class he extends ie{constructor(t){t=at(t,le),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function o(t){const a=t[e.x_axis.field1],o=t[e.x_axis.field2],n=(a+o)/2,r=[[s(a),i(0)],[s(n),i(t[e.y_axis.field])],[s(o),i(0)]];return I.line().x((t=>t[0])).y((t=>t[1])).curve(I.curveNatural)(r)}const n=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(gt,e.style),n.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(n).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>o(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>o(t))),r.exit().remove(),n.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],o=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:o(t.data[s.y_axis.field]),y_max:o(0)}}}const ce={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:12,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class de extends ie{constructor(t){t=at(t,ce),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=I.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const o=I.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,o.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(o).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),o.exit().remove();const n=I.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));n.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(n).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),n.exit().remove();const r=I.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=I.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=I.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const ue={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5,tooltip:null};class _e extends ie{constructor(t){if((t=at(t,ue)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const o=t.x_scale,n=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?I.area().x((t=>+o(t[e]))).y0(+n(0)).y1((t=>+n(t[s]))):I.line().x((t=>+o(t[e]))).y((t=>+n(t[s]))).curve(I[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(gt,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!D.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;void 0===e&&(e=!0),this._global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this._global_statuses).forEach((t=>{this._global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const pe={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class ge extends ie{constructor(t){t=at(t,pe),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments)}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],o=I.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const o=+t[e](s.y);return isNaN(o)?a[i]:o}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",o).call(gt,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=I.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ye={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class fe extends ie{constructor(t){(t=at(t,ye)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),o=Math.sqrt(a/Math.PI);return{x_min:e-o,x_max:e+o,y_min:i-o,y_max:i+o}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,o=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,n=(t,a)=>{const o=+t.attr("x"),n=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",o-n),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",o+n),i&&a.attr("x2",r+l))};t._label_texts.each((function(e,a){const r=I.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>o){const e=i?I.select(t._label_lines.nodes()[a]):null;n(r,e)}})),t._label_texts.each((function(e,o){const r=I.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?I.select(t._label_lines.nodes()[o]):null;t._label_texts.each((function(){const t=I.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(n(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=n.attr("y"),c=.5*(r.topp?(g=d-+o,d=+o,u-=g):u+l.height/2>p&&(g=u-+h,u=+h,d-=g),a.attr("y",d),n.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t._label_texts.nodes();t._label_lines.attr("y2",((t,s)=>I.select(e[s]).attr("y")))}this._label_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let o=this._applyFilters();if(o.forEach((t=>{let o=e(t[this.layout.x_axis.field]),n=s(t[this.layout.y_axis.field]);isNaN(o)&&(o=-1e3),isNaN(n)&&(n=-1e3),t[i]=o,t[a]=n})),this.layout.coalesce.active&&o.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:n,x_gap:r,y_gap:l}=this.layout.coalesce;o=function(t,e,s,i,a,o,n){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function _(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function p(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],f=g>=e&&g<=s&&y>=a&&y<=o;t.lz_is_match||!f?(_(),r.push(t)):null===c?p(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=n?u.push(t):(_(),p(g,y,t))})),_(),r}(o,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(n)?s(+n):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=o.filter(t)}else e=o;this._label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const n=`lz-data_layer-${this.layout.type}-label`,r=this._label_groups.enter().append("g").attr("class",n);this._label_texts&&this._label_texts.remove(),this._label_texts=this._label_groups.merge(r).append("text").text((e=>Ht(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(gt,t.layout.label.style||{}),t.layout.label.lines&&(this._label_lines&&this._label_lines.remove(),this._label_lines=this._label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(gt,t.layout.label.lines.style||{})),this._label_groups.exit().remove()}else this._label_texts&&this._label_texts.remove(),this._label_lines&&this._label_lines.remove(),this._label_groups&&this._label_groups.remove();const n=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(o,(t=>t[this.layout.id_field])),r=I.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>nt(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;n.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(n).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),n.exit().remove(),this.layout.label&&(this.flip_labels(),this._label_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=I.select(I.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class me extends fe{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],o="string"==typeof i?i.toLowerCase():i,n="string"==typeof a?a.toLowerCase():a;return o===n?0:o{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],o=i[e],n=s[a]||[o,o];s[a]=[Math.min(n[0],o),Math.max(n[1],o)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const n=i[t];let r;switch(s){case"left":r=n[0];break;case"center":const t=n[1]-n[0];r=n[0]+(0!==t?t:n[0])/2;break;case"right":r=n[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const be=new c;for(let[t,e]of Object.entries(n))be.add(t,e);const xe=be,ve=7.301,we={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{assoc:variant|htmlescape}}
\n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
\n Ref. Allele: {{assoc:ref_allele|htmlescape}}
\n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
'},$e=function(){const t=ot(we);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),ze={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

{{gene_name|htmlescape}}

Gene ID: {{gene_id|htmlescape}}
Transcript ID: {{transcript_id|htmlescape}}
{{#if pLI}}
ConstraintExpected variantsObserved variantsConst. Metric
Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

{{/if}}More data on gnomAD'},ke={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{catalog:variant|htmlescape}}
Catalog entries: {{n_catalog_matches|htmlescape}}
Top Trait: {{catalog:trait|htmlescape}}
Top P Value: {{catalog:log_pvalue|logtoscinotation}}
More: GWAS catalog / dbSNP'},Ee={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
Promoter
{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
{{#if access:target}}Target: {{access:target|htmlescape}}
{{/if}}Score: {{access:score|htmlescape}}"},Me={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:ve},Se={id:"recombrate",namespace:{recomb:"recomb"},data_operations:[{type:"fetch",from:["recomb"]}],type:"line",tag:"recombination",z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"recomb:position"},y_axis:{axis:2,field:"recomb:recomb_rate",floor:0,ceiling:100}},Ne={namespace:{assoc:"assoc",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)"]},{type:"left_match",name:"assoc_plus_ld",requires:["assoc","ld"],params:["assoc:position","ld:position2"]}],id:"associationpvalues",type:"scatter",tag:"association",id_field:"assoc:variant",coalesce:{active:!0},point_shape:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"diamond"}},{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"assoc:beta",stderr_beta_field:"assoc:se"}},"circle"],point_size:{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:80,else:40}},color:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"#9632b8"}},{scale_function:"numerical_bin",field:"ld:correlation",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{shape:"diamond",color:"#9632b8",size:40,label:"LD Ref Var",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(219, 61, 17)",size:40,label:"1.0 > r² ≥ 0.8",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(248, 195, 42)",size:40,label:"0.8 > r² ≥ 0.6",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(110, 254, 104)",size:40,label:"0.6 > r² ≥ 0.4",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(38, 188, 225)",size:40,label:"0.4 > r² ≥ 0.2",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(70, 54, 153)",size:40,label:"0.2 > r² ≥ 0.0",class:"lz-data_layer-scatter"},{shape:"circle",color:"#AAAAAA",size:40,label:"no r² data",class:"lz-data_layer-scatter"}],label:null,z_index:2,x_axis:{field:"assoc:position"},y_axis:{axis:1,field:"assoc:log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:ot(we)},Ae={id:"coaccessibility",type:"arcs",tag:"coaccessibility",namespace:{access:"access"},data_operations:[{type:"fetch",from:["access"]}],match:{send:"access:target",receive:"access:target"},id_field:"{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}",filters:[{field:"access:score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"access:start1",field2:"access:start2"},y_axis:{axis:1,field:"access:score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:ot(Ee)},Oe=function(){let t=ot(Ne);return t=at({id:"associationpvaluescatalog",fill_opacity:.7},t),t.data_operations.push({type:"assoc_to_gwas_catalog",name:"assoc_catalog",requires:["assoc_plus_ld","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}),t.tooltip.html+='{{#if catalog:rsid}}
See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t}(),Te={id:"phewaspvalues",type:"category_scatter",tag:"phewas",namespace:{phewas:"phewas"},data_operations:[{type:"fetch",from:["phewas"]}],point_shape:[{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"phewas:beta",stderr_beta_field:"phewas:se"}},"circle"],point_size:70,tooltip_positioning:"vertical",id_field:"{{phewas:trait_group}}_{{phewas:trait_label}}",x_axis:{field:"lz_auto_x",category_field:"phewas:trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"phewas:log_pvalue",floor:0,upper_buffer:.15},color:[{field:"phewas:trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Trait: {{phewas:trait_label|htmlescape}}
\nTrait Category: {{phewas:trait_group|htmlescape}}
\nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
β: {{phewas:beta|scinotation|htmlescape}}
{{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}"},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{phewas:trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"phewas:log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},Le={namespace:{gene:"gene",constraint:"constraint"},data_operations:[{type:"fetch",from:["gene","constraint(gene)"]},{name:"gene_constraint",type:"genes_to_gnomad_constraint",requires:["gene","constraint"]}],id:"genes",type:"genes",tag:"genes",id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:ot(ze)},je=at({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},ot(Le)),Pe={namespace:{assoc:"assoc",catalog:"catalog"},data_operations:[{type:"fetch",from:["assoc","catalog"]},{type:"assoc_to_gwas_catalog",name:"assoc_plus_ld",requires:["assoc","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}],id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:"#0000CC",filters:[{field:"catalog:rsid",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:ot(ke),tooltip_positioning:"top"},Re={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},Ie={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},De={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},Ce={widgets:[{type:"title",title:"LocusZoom",subtitle:`v${l}`,position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Be=function(){const t=ot(Ce);return t.widgets.push(ot(Re)),t}(),Ue=function(){const t=ot(Ce);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),qe={id:"association",tag:"association",min_height:200,height:225,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=ot(De);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:28},y2:{label:"Recombination Rate (cM/Mb)",label_offset:40}},legend:{orientation:"vertical",origin:{x:55,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[ot(Me),ot(Se),ot(Ne)]},Fe={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",toolbar:ot(De),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:28,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[ot(Ae)]},He=function(){let t=ot(qe);return t=at({id:"associationcatalog"},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{catalog:trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"catalog:trait",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve},{field:"ld:correlation",operator:">",value:.4}],style:{"font-size":"10px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[ot(Me),ot(Se),ot(Oe)],t}(),Ge={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:50,bottom:20,left:50},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=ot(De);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},ot(Ie)),t}(),data_layers:[ot(je)]},Je={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:50,bottom:120,left:50},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:28}},data_layers:[ot(Me),ot(Te)]},Ze={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:50,bottom:10,left:50},inner_border:"rgb(210, 210, 210)",toolbar:ot(De),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[ot(Pe)]},Ke={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[ot(qe),ot(Ge)]},Ve={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[Ze,He,Ge]},We={width:800,responsive_resize:!0,toolbar:Ce,panels:[ot(Je),at({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"}}},ot(Ge))],mouse_guide:!1},Ye={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:ot(Ce),panels:[ot(Fe),function(){const t=Object.assign({height:270},ot(Ge)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},Xe={standard_association:we,standard_association_with_label:$e,standard_genes:ze,catalog_variant:ke,coaccessibility:Ee},Qe={ldlz2_pop_selector:Re,gene_selector_menu:Ie},ts={standard_panel:De,standard_plot:Ce,standard_association:Be,region_nav_plot:Ue},es={significance:Me,recomb_rate:Se,association_pvalues:Ne,coaccessibility:Ae,association_pvalues_catalog:Oe,phewas_pvalues:Te,genes:Le,genes_filtered:je,annotation_catalog:Pe},ss={association:qe,coaccessibility:Fe,association_catalog:He,genes:Ge,phewas:Je,annotation_catalog:Ze},is={standard_association:Ke,association_catalog:Ve,standard_phewas:We,coaccessibility:Ye};const as=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);const a=s.namespace;i.namespace||delete s.namespace;let o=at(s,i);return a&&(o=it(o,a)),ot(o)}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=ot(s);return"data_layer"===t&&a.namespace&&(a._auto_fields=[...rt(a,Object.keys(a.namespace))].sort()),super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return at(t,e)}renameField(){return lt(...arguments)}mutate_attrs(){return ht(...arguments)}query_attrs(){return ct(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))as.add(t,s,i);const os=as,ns=new h;function rs(t){return(e,s,...i)=>{if(2!==s.length)throw new Error("Join functions must receive exactly two recordsets");return t(...s,...i)}}ns.add("left_match",rs(x)),ns.add("inner_match",rs((function(t,e,s,i){return b("inner",...arguments)}))),ns.add("full_outer_match",rs((function(t,e,s,i){return b("outer",...arguments)}))),ns.add("assoc_to_gwas_catalog",rs((function(t,e,s,i,a){if(!t.length)return t;const o=m(e,i),n=[];for(let t of o.values()){let e,s=0;for(let i of t){const t=i[a];t>=s&&(e=i,s=t)}e.n_catalog_matches=t.length,n.push(e)}return x(t,n,s,i)}))),ns.add("genes_to_gnomad_constraint",rs((function(t,e){return t.forEach((function(t){const s=`_${t.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=e[s]&&e[s].gnomad_constraint;i&&Object.keys(i).forEach((function(e){let s=i[e];void 0===t[e]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),t[e]=s)}))})),t})));const ls=ns;const hs={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return I.select(t).html(""),I.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!I.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Ut(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Ft(s[2]),e=Ft(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Ft(s[2]),end:Ft(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Ft(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=I.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(gt,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||R}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:R,DataLayers:xe,DataFunctions:ls,Layouts:os,MatchFunctions:Jt,ScaleFunctions:ee,TransformationFunctions:Z,Widgets:jt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),R}},cs=[];hs.use=function(t,...e){if(!cs.includes(t)){if(e.unshift(hs),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}cs.push(t)}};const ds=hs})(),LocusZoom=i.default})(); //# sourceMappingURL=locuszoom.app.min.js.map \ No newline at end of file diff --git a/dist/locuszoom.app.min.js.map b/dist/locuszoom.app.min.js.map index b4aed111..0648c2f0 100644 --- a/dist/locuszoom.app.min.js.map +++ b/dist/locuszoom.app.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","RegistryBase","this","_items","Map","name","has","Error","item","override","set","delete","Array","from","keys","ClassRegistry","args","parent_name","source_name","overrides","console","warn","arguments","length","base","sub","assign","add","validateBuildSource","class_name","build","source","includes","BaseAdapter","config","_enableCache","_cachedKey","_cache_pos_start","_cache_pos_end","__dependentSource","parseInit","params","state","chain","fields","getURL","cache_pos_chr","chr","start","end","url","fetch","then","response","ok","statusText","text","req","cacheKey","getCacheKey","Promise","resolve","_cachedResponse","fetchRequest","data","isArray","N","every","constructor","records","i","record","j","push","outnames","trans","fieldFound","k","map","output_record","val","forEach","v","resp","source_id","discrete","json","JSON","parse","normalizeResponse","standardized","annotateData","extractFields","one_source_body","combineChainBody","new_body","header","body","preGetData","pre","getRequest","parseResponse","BaseApiAdapter","super","AssociationLZ","id_field","x","unshift","analysis","sort","a","b","LDServer","join","dataFields","id","position","position_field","pvalue","pvalue_field","_names_","names","nameMatch","arr","regexes","regex","m","filter","match","id_match","RegExp","isrefvarin","isrefvarout","ldin","ldout","refVar","findRequestedFields","ldrefvar","findMergeFields","columns","pval_field","cmp","test","extremeVal","extremeIdx","findExtremeValue","original","chrom","pos","ref","alt","refVar_formatted","genome_build","ld_source","population","ld_pop","method","refVar_raw","getRefvar","encodeURIComponent","_","reqFields","corrField","rsquare","left","right","lfield","rfield","position2","leftJoin","refvar","idfield","outrefname","outldname","tagRefVariant","combined","chainRequests","payload","concat","next","GwasCatalogLZ","source_query","posMatch","find","decider","decider_out","indexOf","n_matches","fn","outn","chainNames","catNames","GeneLZ","GeneConstraintLZ","unique_gene_names","reduce","acc","gene","gene_name","query","replace","stringify","headers","catch","err","alias","constraint","toString","parseFloat","toFixed","RecombLZ","SOURCE_NAME","StaticSource","_data","PheWASLZ","variant","ConnectorSource","sources","_source_name_mapping","specified_ids","_getRequiredSources","ns","chain_source_id","registry","type","entries","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","parts","exec","full_name","namespace","transformations","split","transform","transforms","extra","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","e","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","slice","paths","d","undefined","p","__","subject","uniqPaths","elem","values","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","element","default_namespace","default","re","resolved_namespace","r","merge","namespaced_element","namespaced_property","property","custom_layout","default_layout","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","renameField","layout","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","_sources","requests","raw","__split_requests","request_handles","getData","ret","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","node","parentNode","insert","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","width","delay","setTimeout","remove","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","result","index","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","apply","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","old","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","children","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","row","radioId","field_name","has_option","choice","defaultName","default_config_display_name","options","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","dashboard","components","widget","create","error","panel_boundaries","dragging","interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","group","line_height","data_layer_ids_by_z_index","reverse","label_x","label_y","shape_factory","path_y","radius","PI","label","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","generateID","initialized","layout_idx","state_id","data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","zoom_timeout","event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","y_axis","axis","data_layer","z_index","dlid","idx","data_layer_layout","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","base_x_range","range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","scale","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","domain","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","message","all","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tickValues","tick_format","tickFormat","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","mouse_guide","Plot","datasource","remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","layer_state","_setDefaultState","clearPanelData","success_callback","opts","error_callback","onerror","listener","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","some","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","resize_listener","rescaleSVG","window","addEventListener","trackExternalListener","load_listener","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","positionStringToInt","suffixre","mult","tokens","condition","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","input","field_value","numerical_bin","breaks","null_value","threshold","prev","curr","categorical_bin","categories","ordinal_cycle","stable_choice","_cache","max_cache_size","clear","hash","String","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","tooltips","global_statuses","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","lz_is_match","toHTML","getDataLayer","getPanel","getPlot","deselect","unselectElement","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","Set","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","label_texts","da","dal","label_lines","nodes","dax","abound","bbound","seperate_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","unnamespaced","contents","list","LocusZoom","version","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","Adapters","DataLayers","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.13.4';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", {url: \"http://server.com/api/single/\", params: {source: 1}}]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", {url: \"http://server.com/api/single/\", params: {source: 2}}]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nfunction validateBuildSource(class_name, build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${class_name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${class_name} must specify a valid genome build number`);\n }\n}\n\n\n// NOTE: Custom adapaters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs.\n// Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal\n// methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the\n// private API methods exist in the base class.\n\n/**\n * Base class for LocusZoom data sources (any). See also: BaseApiAdapter for requests from a remote URL.\n * @public\n */\nclass BaseAdapter {\n /**\n * @param {object} config Configuration options\n */\n constructor(config) {\n /**\n * Whether to enable caching (true for most data adapters)\n * @private\n * @member {Boolean}\n */\n this._enableCache = true;\n this._cachedKey = null;\n\n // Almost all LZ sources are \"region based\". Cache the region requested and use it to determine whether\n // the cache would satisfy the request.\n this._cache_pos_start = null;\n this._cache_pos_end = null;\n\n /**\n * Whether this adapter type is dependent on previous requests- for example, the LD source cannot annotate\n * association data if no data was found for that region.\n * @private\n * @member {boolean}\n */\n this.__dependentSource = false;\n\n // Parse configuration options\n this.parseInit(config);\n }\n\n /**\n * Parse configuration used to create the instance. Many custom sources will override this method to suit their\n * needs (eg specific config options, or for sources that do not retrieve data from a URL)\n * @protected\n * @param {String|Object} config Basic configuration- either a url, or a config object\n * @param {String} [config.url] The datasource URL\n * @param {String} [config.params] Initial config params for the datasource\n */\n parseInit(config) {\n /**\n * @private\n * @member {Object}\n */\n this.params = config.params || {};\n }\n\n /**\n * A unique identifier that indicates whether cached data is valid for this request. For most sources using GET\n * requests to a REST API, this is usually the region requested. Some sources will append additional params to define the request.\n *\n * This means that to change caching behavior, both the URL and the cache key may need to be updated. However,\n * it allows most datasources to skip an extra network request when zooming in.\n * @protected\n * @param {Object} state Information available in plot.state (chr, start, end). Sometimes used to inject globally\n * available information that influences the request being made.\n * @param {Object} chain The data chain from previous requests made in a sequence.\n * @param fields\n * @returns {String}\n */\n getCacheKey(state, chain, fields) {\n // Most region sources, by default, will cache the largest region that satisfies the request: zooming in\n // should be satisfied via the cache, but pan or region change operations will cause a network request\n\n // Some adapters rely on values set in chain.header during the getURL call. (eg, the LD source uses\n // this to find the LD refvar) Calling this method is a backwards-compatible way of ensuring that value is set,\n // even on a cache hit in which getURL otherwise wouldn't be called.\n // Some of the adapters that rely on this behavior are user-defined, hence compatibility hack\n this.getURL(state, chain, fields);\n\n const cache_pos_chr = state.chr;\n const {_cache_pos_start, _cache_pos_end} = this;\n if (_cache_pos_start && state.start >= _cache_pos_start && _cache_pos_end && state.end <= _cache_pos_end ) {\n return `${cache_pos_chr}_${_cache_pos_start}_${_cache_pos_end}`;\n } else {\n return `${state.chr}_${state.start}_${state.end}`;\n }\n }\n\n /**\n * Stub: build the URL for any requests made by this source.\n * @protected\n */\n getURL(state, chain, fields) {\n return this.url;\n }\n\n /**\n * Perform a network request to fetch data for this source. This is usually the method that is used to override\n * when defining how to retrieve data.\n * @protected\n * @param {Object} state The state of the parent plot\n * @param chain\n * @param fields\n * @returns {Promise}\n */\n fetchRequest(state, chain, fields) {\n const url = this.getURL(state, chain, fields);\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n });\n }\n\n /**\n * Gets the data for just this source, typically via a network request (but using cache where possible)\n *\n * For most use cases, it is better to override `fetchRequest` instead, to avoid bypassing the cache mechanism\n * by accident.\n * @protected\n * @return {Promise}\n */\n getRequest(state, chain, fields) {\n let req;\n const cacheKey = this.getCacheKey(state, chain, fields);\n\n if (this._enableCache && typeof(cacheKey) !== 'undefined' && cacheKey === this._cachedKey) {\n req = Promise.resolve(this._cachedResponse); // Resolve to the value of the current promise\n } else {\n req = this.fetchRequest(state, chain, fields);\n if (this._enableCache) {\n this._cachedKey = cacheKey;\n this._cache_pos_start = state.start;\n this._cache_pos_end = state.end;\n this._cachedResponse = req;\n }\n }\n return req;\n }\n\n /**\n * Ensure the server response is in a canonical form, an array of one object per record. [ {field: oneval} ].\n * If the server response contains columns, reformats the response from {column1: [], column2: []} to the above.\n *\n * Does not apply namespacing, transformations, or field extraction.\n *\n * May be overridden by data adapters that inherently return more complex payloads, or that exist to annotate other\n * sources (eg, if the payload provides extra data rather than a series of records).\n * @protected\n * @param {Object[]|Object} data The original parsed server response\n */\n normalizeResponse(data) {\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the rows, and create an object for each record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n\n /**\n * Hook to post-process the data returned by this source with new, additional behavior.\n * (eg cleaning up API values or performing complex calculations on the returned data)\n *\n * @protected\n * @param {Object[]} records The parsed data from the source (eg standardized api response)\n * @param {Object} chain The data chain object. For example, chain.headers may provide useful annotation metadata\n * @returns {Object[]|Promise} The modified set of records\n */\n annotateData(records, chain) {\n // Default behavior: no transformations\n return records;\n }\n\n /**\n * Clean up the server records for use by datalayers: extract only certain fields, with the specified names.\n * Apply per-field transformations as appropriate.\n *\n * This hook can be overridden, eg to create a source that always returns all records and ignores the \"fields\" array.\n * This is particularly common for sources at the end of a chain- many \"dependent\" sources do not allow\n * cherry-picking individual fields, in which case by **convention** the fields array specifies \"last_source_name:all\"\n *\n * @protected\n * @param {Object[]} data One record object per element\n * @param {String[]} fields The names of fields to extract (as named in the source data). Eg \"afield\"\n * @param {String[]} outnames How to represent the source fields in the output. Eg \"namespace:afield|atransform\"\n * @param {function[]} trans An array of transformation functions (if any). One function per data element, or null.\n * @protected\n */\n extractFields (data, fields, outnames, trans) {\n //intended for an array of objects\n // [ {\"id\":1, \"val\":5}, {\"id\":2, \"val\":10}]\n // Since a number of sources exist that do not obey this format, we will provide a convenient pass-through\n if (!Array.isArray(data)) {\n return data;\n }\n\n if (!data.length) {\n // Sometimes there are regions that just don't have data- this should not trigger a missing field error message!\n return data;\n }\n\n const fieldFound = [];\n for (let k = 0; k < fields.length; k++) {\n fieldFound[k] = 0;\n }\n\n const records = data.map(function (item) {\n const output_record = {};\n for (let j = 0; j < fields.length; j++) {\n let val = item[fields[j]];\n if (typeof val != 'undefined') {\n fieldFound[j] = 1;\n }\n if (trans && trans[j]) {\n val = trans[j](val);\n }\n output_record[outnames[j]] = val;\n }\n return output_record;\n });\n fieldFound.forEach(function(v, i) {\n if (!v) {\n throw new Error(`field ${fields[i]} not found in response for ${outnames[i]}`);\n }\n });\n return records;\n }\n\n /**\n * Combine records from this source with others in the chain to yield final chain body.\n * Handles merging this data with other sources (if applicable).\n *\n * @protected\n * @param {Object[]} data The data That would be returned from this source alone\n * @param {Object} chain The data chain built up during previous requests\n * @param {String[]} fields\n * @param {String[]} outnames\n * @param {String[]} trans\n * @return {Promise|Object[]} The new chain body\n */\n combineChainBody(data, chain, fields, outnames, trans) {\n return data;\n }\n\n /**\n * Coordinates the work of parsing a response and returning records. This is broken into 4 steps, which may be\n * overridden separately for fine-grained control. Each step can return either raw data or a promise.\n *\n * @see {module:LocusZoom_Adapters~BaseAdapter#normalizeResponse}\n * @see {module:LocusZoom_Adapters~BaseAdapter#annotateData}\n * @see {module:LocusZoom_Adapters~BaseAdapter#extractFields}\n * @see {module:LocusZoom_Adapters~BaseAdapter#combineChainBody}\n * @protected\n *\n * @param {String|Object} resp The raw data associated with the response\n * @param {Object} chain The combined parsed response data from this and all other requests made in the chain\n * @param {String[]} fields Array of requested field names (as they would appear in the response payload)\n * @param {String[]} outnames Array of field names as they will be represented in the data returned by this source,\n * including the namespace. This must be an array with the same length as `fields`\n * @param {Function[]} trans The collection of transformation functions to be run on selected fields.\n * This must be an array with the same length as `fields`\n * @returns {Promise} A promise that resolves to an object containing\n * request metadata (`headers: {}`), the consolidated data for plotting (`body: []`), and the individual responses that would be\n * returned by each source in the chain in isolation (`discrete: {}`)\n */\n parseResponse (resp, chain, fields, outnames, trans) {\n const source_id = this.source_id || this.constructor.name;\n if (!chain.discrete) {\n chain.discrete = {};\n }\n\n const json = typeof resp == 'string' ? JSON.parse(resp) : resp;\n\n // Perform the 4 steps of parsing the payload and return a combined chain object\n return Promise.resolve(this.normalizeResponse(json.data || json))\n .then((standardized) => {\n // Perform calculations on the data from just this source\n return Promise.resolve(this.annotateData(standardized, chain));\n }).then((data) => {\n return Promise.resolve(this.extractFields(data, fields, outnames, trans));\n }).then((one_source_body) => {\n // Store a copy of the data that would be returned by parsing this source in isolation (and taking the\n // fields array into account). This is useful when we want to re-use the source output in many ways.\n chain.discrete[source_id] = one_source_body;\n return Promise.resolve(this.combineChainBody(one_source_body, chain, fields, outnames, trans));\n }).then((new_body) => {\n return { header: chain.header || {}, discrete: chain.discrete, body: new_body };\n });\n }\n\n /**\n * Fetch the data from the specified data adapter, and apply transformations requested by an external consumer.\n * This is the public-facing datasource method that will most be called by the plot, but custom data adapters will\n * almost never want to override this method directly- more specific hooks are provided to control individual pieces\n * of the request lifecycle.\n *\n * @private\n * @param {Object} state The current \"state\" of the plot, such as chromosome and start/end positions\n * @param {String[]} fields Array of field names that the plot has requested from this data adapter. (without the \"namespace\" prefix)\n * @param {String[]} outnames Array describing how the output data should refer to this field. This represents the\n * originally requested field name, including the namespace. This must be an array with the same length as `fields`\n * @param {Function[]} trans The collection of transformation functions to be run on selected fields.\n * This must be an array with the same length as `fields`\n * @returns {function} A callable operation that can be used as part of the data chain\n */\n getData(state, fields, outnames, trans) {\n if (this.preGetData) { // TODO try to remove this method if at all possible\n const pre = this.preGetData(state, fields, outnames, trans);\n if (this.pre) {\n state = pre.state || state;\n fields = pre.fields || fields;\n outnames = pre.outnames || outnames;\n trans = pre.trans || trans;\n }\n }\n\n return (chain) => {\n if (this.__dependentSource && chain && chain.body && !chain.body.length) {\n // A \"dependent\" source should not attempt to fire a request if there is no data for it to act on.\n // Therefore, it should simply return the previous data chain.\n return Promise.resolve(chain);\n }\n\n return this.getRequest(state, chain, fields).then((resp) => {\n return this.parseResponse(resp, chain, fields, outnames, trans);\n });\n };\n }\n}\n\n/**\n * Base class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {\n parseInit(config) {\n super.parseInit(config);\n\n /**\n * @private\n * @member {String}\n */\n this.url = config.url;\n if (!this.url) {\n throw new Error('Source not initialized with required URL');\n }\n }\n}\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseApiAdapter\n * @param {string} config.url The base URL for the remote data.\n * @param {object} config.params\n * @param [config.params.sort=false] Whether to sort the association data (by an assumed field named \"position\"). This\n * is primarily a site-specific workaround for a particular LZ usage; we encourage apis to sort by position before returning\n * data to the browser.\n * @param [config.params.source] The ID of the GWAS dataset to use for this request, as matching the API backend\n */\nclass AssociationLZ extends BaseApiAdapter {\n preGetData (state, fields, outnames, trans) {\n // TODO: Modify internals to see if we can go without this method\n const id_field = this.params.id_field || 'id';\n [id_field, 'position'].forEach(function(x) {\n if (!fields.includes(x)) {\n fields.unshift(x);\n outnames.unshift(x);\n trans.unshift(null);\n }\n });\n return {fields: fields, outnames:outnames, trans:trans};\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n getURL (state, chain, fields) {\n const analysis = chain.header.analysis || this.params.source || this.params.analysis; // Old usages called this param \"analysis\"\n if (typeof analysis == 'undefined') {\n throw new Error('Association source must specify an analysis ID to plot');\n }\n return `${this.url}results/?filter=analysis in ${analysis} and chromosome in '${state.chr}' and position ge ${state.start} and position le ${state.end}`;\n }\n\n /**\n * Some association sources do not sort their data in a predictable order, which makes it hard to reliably\n * align with other sources (such as LD). For performance reasons, sorting is an opt-in argument.\n * TODO: Consider more fine grained sorting control in the future. This was added as a very specific\n * workaround for the original T2D portal.\n * @protected\n * @param data\n * @return {Object}\n */\n normalizeResponse (data) {\n data = super.normalizeResponse(data);\n if (this.params && this.params.sort && data.length && data[0]['position']) {\n data.sort(function (a, b) {\n return a['position'] - b['position'];\n });\n }\n return data;\n }\n}\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant (smallest pvalue or largest neg_log_pvalue) and yse that as the LD reference variant.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request in the data chain. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseApiAdapter\n */\nclass LDServer extends BaseApiAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param {object} config.params\n * @param [config.params.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.params.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.params.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.params.method='rsquare'] The metric used to calculate LD\n * @param [config.params.id_field] The association data field that contains variant identifier information. The preferred\n * format of LD server is `chrom:pos_ref/alt` and this source will attempt to normalize other common formats.\n * This source can auto-detect possible matches for field names containing \"variant\" or \"id\"\n * @param [config.params.position_field] The association data field that contains variant position information.\n * This source can auto-detect possible matches for field names containing \"position\" or \"pos\"\n * @param [config.params.pvalue_field] The association data field that contains pvalue information.\n * This source can auto-detect possible matches for field names containing \"pvalue\" or \"log_pvalue\".\n * The suggested LD refvar will be the smallest pvalue, or the largest log_pvalue: this source will auto-detect\n * the word \"log\" in order to determine the sign of the comparison.\n */\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\n\n preGetData(state, fields) {\n if (fields.length > 1) {\n if (fields.length !== 2 || !fields.includes('isrefvar')) {\n throw new Error(`LD does not know how to get all fields: ${fields.join(', ')}`);\n }\n }\n }\n\n findMergeFields(chain) {\n // Find the fields (as provided by a previous step in the chain, like an association source) that will be needed to\n // combine LD data with existing information\n\n // Since LD information may be shared across multiple assoc sources with different namespaces,\n // we use regex to find columns to join on, rather than requiring exact matches\n const exactMatch = function (arr) {\n return function () {\n const regexes = arguments;\n for (let i = 0; i < regexes.length; i++) {\n const regex = regexes[i];\n const m = arr.filter(function (x) {\n return x.match(regex);\n });\n if (m.length) {\n return m[0];\n }\n }\n return null;\n };\n };\n let dataFields = {\n id: this.params.id_field,\n position: this.params.position_field,\n pvalue: this.params.pvalue_field,\n _names_:null,\n };\n if (chain && chain.body && chain.body.length > 0) {\n const names = Object.keys(chain.body[0]);\n const nameMatch = exactMatch(names);\n // Internally, fields are generally prefixed with the name of the source they come from.\n // If the user provides an id_field (like `variant`), it should work across data sources (`assoc1:variant`,\n // assoc2:variant), but not match fragments of other field names (assoc1:variant_thing)\n // Note: these lookups hard-code a couple of common fields that will work based on known APIs in the wild\n const id_match = dataFields.id && nameMatch(new RegExp(`${dataFields.id}\\\\b`));\n dataFields.id = id_match || nameMatch(/\\bvariant\\b/) || nameMatch(/\\bid\\b/);\n dataFields.position = dataFields.position || nameMatch(/\\bposition\\b/i, /\\bpos\\b/i);\n dataFields.pvalue = dataFields.pvalue || nameMatch(/\\bpvalue\\b/i, /\\blog_pvalue\\b/i);\n dataFields._names_ = names;\n }\n return dataFields;\n }\n\n findRequestedFields (fields, outnames) {\n // Assumption: all usages of this source will only ever ask for \"isrefvar\" or \"state\". This maps to output names.\n let obj = {};\n for (let i = 0; i < fields.length; i++) {\n if (fields[i] === 'isrefvar') {\n obj.isrefvarin = fields[i];\n obj.isrefvarout = outnames && outnames[i];\n } else {\n obj.ldin = fields[i];\n obj.ldout = outnames && outnames[i];\n }\n }\n return obj;\n }\n\n /**\n * The LD API payload does not obey standard format conventions; do not try to transform it.\n */\n normalizeResponse (data) {\n return data;\n }\n\n /**\n * Get the LD reference variant, which by default will be the most significant hit in the assoc results\n * This will be used in making the original query to the LD server for pairwise LD information.\n *\n * This is meant to join a single LD request to any number of association results, and to work with many kinds of API.\n * To do this, the datasource looks for fields with special known names such as pvalue, log_pvalue, etc.\n * If your API uses different nomenclature, an option must be specified.\n *\n * @protected\n * @returns {String[]} Two strings: 1) the marker id (expected to be in `chr:pos_ref/alt` format) of the reference\n * variant, and 2) the marker ID as it appears in the original dataset that we are joining to, so that the exact\n * refvar can be marked when plotting the data..\n */\n getRefvar(state, chain, fields) {\n let findExtremeValue = function(records, pval_field) {\n // Finds the most significant hit (smallest pvalue, or largest -log10p). Will try to auto-detect the appropriate comparison.\n pval_field = pval_field || 'log_pvalue'; // The official LZ API returns log_pvalue\n const is_log = /log/.test(pval_field);\n let cmp;\n if (is_log) {\n cmp = function(a, b) {\n return a > b;\n };\n } else {\n cmp = function(a, b) {\n return a < b;\n };\n }\n let extremeVal = records[0][pval_field], extremeIdx = 0;\n for (let i = 1; i < records.length; i++) {\n if (cmp(records[i][pval_field], extremeVal)) {\n extremeVal = records[i][pval_field];\n extremeIdx = i;\n }\n }\n return extremeIdx;\n };\n\n let reqFields = this.findRequestedFields(fields);\n let refVar = reqFields.ldin;\n if (refVar === 'state') {\n refVar = state.ldrefvar || chain.header.ldrefvar || 'best';\n }\n if (refVar === 'best') {\n if (!chain.body) {\n throw new Error('No association data found to find best pvalue');\n }\n let keys = this.findMergeFields(chain);\n if (!keys.pvalue || !keys.id) {\n let columns = '';\n if (!keys.id) {\n columns += `${columns.length ? ', ' : ''}id`;\n }\n if (!keys.pvalue) {\n columns += `${columns.length ? ', ' : ''}pvalue`;\n }\n throw new Error(`Unable to find necessary column(s) for merge: ${columns} (available: ${keys._names_})`);\n }\n refVar = chain.body[findExtremeValue(chain.body, keys.pvalue)][keys.id];\n }\n // Some datasets, notably the Portal, use a different marker format.\n // Coerce it into one that will work with the LDServer API. (CHROM:POS_REF/ALT)\n const REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n const match = refVar && refVar.match(REGEX_MARKER);\n\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n const [original, chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n let refVar_formatted = `${chrom}:${pos}`;\n if (ref && alt) {\n refVar_formatted += `_${ref}/${alt}`;\n }\n\n return [refVar_formatted, original];\n }\n\n /**\n * Identify (or guess) the LD reference variant, then add query parameters to the URL to construct a query for the specified region\n */\n getURL(state, chain, fields) {\n // The LD source/pop can be overridden from plot.state for dynamic layouts\n const build = state.genome_build || this.params.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let source = state.ld_source || this.params.source || '1000G';\n const population = state.ld_pop || this.params.population || 'ALL'; // LDServer panels will always have an ALL\n const method = this.params.method || 'rsquare';\n\n if (source === '1000G' && build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n source = '1000G-FRZ09';\n }\n\n validateBuildSource(this.constructor.name, build, null); // LD doesn't need to validate `source` option\n\n const [refVar_formatted, refVar_raw] = this.getRefvar(state, chain, fields);\n\n // Preserve the user-provided variant spec for use when matching to assoc data\n chain.header.ldrefvar = refVar_raw;\n\n return [\n this.url, 'genome_builds/', build, '/references/', source, '/populations/', population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(refVar_formatted),\n '&chrom=', encodeURIComponent(state.chr),\n '&start=', encodeURIComponent(state.start),\n '&stop=', encodeURIComponent(state.end),\n ].join('');\n }\n\n /**\n * The LD adapter caches based on region, reference panel, and population name\n * @param state\n * @param chain\n * @param fields\n * @return {string}\n */\n getCacheKey(state, chain, fields) {\n const base = super.getCacheKey(state, chain, fields);\n let source = state.ld_source || this.params.source || '1000G';\n const population = state.ld_pop || this.params.population || 'ALL'; // LDServer panels will always have an ALL\n const [refVar, _] = this.getRefvar(state, chain, fields);\n return `${base}_${refVar}_${source}_${population}`;\n }\n\n /**\n * The LD adapter attempts to intelligently match retrieved LD information to a request for association data earlier in the data chain.\n * Since each layer only asks for the data needed for that layer, one LD call is sufficient to annotate many separate association tracks.\n */\n combineChainBody(data, chain, fields, outnames, trans) {\n let keys = this.findMergeFields(chain);\n let reqFields = this.findRequestedFields(fields, outnames);\n if (!keys.position) {\n throw new Error(`Unable to find position field for merge: ${keys._names_}`);\n }\n const leftJoin = function (left, right, lfield, rfield) {\n let i = 0, j = 0;\n while (i < left.length && j < right.position2.length) {\n if (left[i][keys.position] === right.position2[j]) {\n left[i][lfield] = right[rfield][j];\n i++;\n j++;\n } else if (left[i][keys.position] < right.position2[j]) {\n i++;\n } else {\n j++;\n }\n }\n };\n const tagRefVariant = function (data, refvar, idfield, outrefname, outldname) {\n for (let i = 0; i < data.length; i++) {\n if (data[i][idfield] && data[i][idfield] === refvar) {\n data[i][outrefname] = 1;\n data[i][outldname] = 1; // For label/filter purposes, implicitly mark the ref var as LD=1 to itself\n } else {\n data[i][outrefname] = 0;\n }\n }\n };\n\n // LD servers vary slightly. Some report corr as \"rsquare\", others as \"correlation\"\n let corrField = data.rsquare ? 'rsquare' : 'correlation';\n leftJoin(chain.body, data, reqFields.ldout, corrField);\n if (reqFields.isrefvarin && chain.header.ldrefvar) {\n tagRefVariant(chain.body, chain.header.ldrefvar, keys.id, reqFields.isrefvarout, reqFields.ldout);\n }\n return chain.body;\n }\n\n /**\n * The LDServer API is paginated, but we need all of the data to render a plot. Depaginate and combine where appropriate.\n */\n fetchRequest(state, chain, fields) {\n let url = this.getURL(state, chain, fields);\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseApiAdapter\n */\nclass GwasCatalogLZ extends BaseApiAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param {Object} config.params\n * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.params.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n getURL(state, chain, fields) {\n // This is intended to be aligned with another source- we will assume they are always ordered by position, asc\n // (regardless of the actual match field)\n const build = state.genome_build || this.params.build;\n const source = this.params.source;\n validateBuildSource(this.constructor.name, build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n return `${this.url }?format=objects&sort=pos&filter=chrom eq '${state.chr}' and pos ge ${state.start} and pos le ${state.end}${source_query}`;\n }\n\n findMergeFields(records) {\n // Data from previous sources is already namespaced. Find the alignment field by matching.\n const knownFields = Object.keys(records);\n // Note: All API endoints involved only give results for 1 chromosome at a time; match is implied\n const posMatch = knownFields.find(function (item) {\n return item.match(/\\b(position|pos)\\b/i);\n });\n\n if (!posMatch) {\n throw new Error('Could not find data to align with GWAS catalog results');\n }\n return { 'pos': posMatch };\n }\n\n extractFields (data, fields, outnames, trans) {\n // Skip the \"individual field extraction\" step; extraction will be handled when building chain body instead\n return data;\n }\n\n /**\n * Intelligently combine the LD data with the association data used for this data layer. See class description\n * for a summary of how this works.\n */\n combineChainBody(data, chain, fields, outnames, trans) {\n if (!data.length) {\n return chain.body;\n }\n\n // TODO: Better reuse options in the future. This source is very specifically tied to the UM PortalDev API, where\n // the field name is always \"log_pvalue\". Relatively few sites will write their own gwas-catalog endpoint.\n const decider = 'log_pvalue';\n const decider_out = outnames[fields.indexOf(decider)];\n\n function leftJoin(left, right, fields, outnames, trans) { // Add `fields` from `right` to `left`\n // Add a synthetic, un-namespaced field to all matching records\n const n_matches = left['n_catalog_matches'] || 0;\n left['n_catalog_matches'] = n_matches + 1;\n if (decider && left[decider_out] && left[decider_out] > right[decider]) {\n // There may be more than one GWAS catalog entry for the same SNP. This source is intended for a 1:1\n // annotation scenario, so for now it only joins the catalog entry that has the best -log10 pvalue\n return;\n }\n\n for (let j = 0; j < fields.length; j++) {\n const fn = fields[j];\n const outn = outnames[j];\n\n let val = right[fn];\n if (trans && trans[j]) {\n val = trans[j](val);\n }\n left[outn] = val;\n }\n }\n\n const chainNames = this.findMergeFields(chain.body[0]);\n const catNames = this.findMergeFields(data[0]);\n\n var i = 0, j = 0;\n while (i < chain.body.length && j < data.length) {\n var left = chain.body[i];\n var right = data[j];\n\n if (left[chainNames.pos] === right[catNames.pos]) {\n // There may be multiple catalog entries for each matching SNP; evaluate match one at a time\n leftJoin(left, right, fields, outnames, trans);\n j += 1;\n } else if (left[chainNames.pos] < right[catNames.pos]) {\n i += 1;\n } else {\n j += 1;\n }\n }\n return chain.body;\n }\n}\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseApiAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {Object} config.params\n * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.params.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseApiAdapter {\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n getURL(state, chain, fields) {\n const build = state.genome_build || this.params.build;\n let source = this.params.source;\n validateBuildSource(this.constructor.name, build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n return `${this.url}?filter=chrom eq '${state.chr}' and start le ${state.end} and end ge ${state.start}${source_query}`;\n }\n\n /**\n * The UM genes API has a very complex internal data format. Bypass any record parsing, and provide the data layer with\n * the exact information returned by the API. (ignoring the fields array in the layout)\n * @param data\n * @return {Object[]|Object}\n */\n normalizeResponse(data) {\n return data;\n }\n\n /**\n * Does not attempt to namespace or modify the fields from the API payload; the payload format is very complex and\n * quite coupled with the data rendering implementation.\n * Typically, requests to this adapter specify a single dummy field sufficient to trigger the request: `fields:[ 'gene:all' ]`\n */\n extractFields(data, fields, outnames, trans) {\n return data;\n }\n}\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseApiAdapter\n */\nclass GeneConstraintLZ extends BaseApiAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param {Object} config.params\n * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\n\n /**\n * GraphQL API: request details are encoded in the body, not the URL\n */\n getURL() {\n return this.url;\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing, and provide the data layer with\n * the exact information returned by the API.\n */\n normalizeResponse(data) {\n return data;\n }\n\n fetchRequest(state, chain, fields) {\n const build = state.genome_build || this.params.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = chain.body.reduce(\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n function (acc, gene) {\n acc[gene.gene_name] = null;\n return acc;\n },\n {}\n );\n let query = Object.keys(unique_gene_names).map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve({ data: null });\n }\n\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n const url = this.getURL(state, chain, fields);\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * Annotate GENCODE data (from a previous request to the genes adapter) with additional gene constraint data from\n * the gnomAD API. See class description for a summary of how this works.\n */\n combineChainBody(data, chain, fields, outnames, trans) {\n if (!data) {\n return chain;\n }\n\n chain.body.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = data[alias] && data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return chain.body;\n }\n}\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseApiAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {Object} config.params\n * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.params.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseApiAdapter {\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n getURL(state, chain, fields) {\n const build = state.genome_build || this.params.build;\n let source = this.params.source;\n validateBuildSource(this.constructor.SOURCE_NAME, build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n return `${this.url}?filter=chromosome eq '${state.chr}' and position le ${state.end} and position ge ${state.start}${source_query}`;\n }\n}\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseAdapter\n * @param {object} data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseAdapter {\n parseInit(data) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n this._data = data;\n }\n\n getRequest(state, chain, fields) {\n return Promise.resolve(this._data);\n }\n}\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseApiAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {Object} config.params\n * @param {String[]} config.params.build This datasource expects to be provided the name of the genome build that will\n * be used to provide pheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseApiAdapter {\n getURL(state, chain, fields) {\n const build = (state.genome_build ? [state.genome_build] : null) || this.params.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.SOURCE_NAME, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const url = [\n this.url,\n \"?filter=variant eq '\", encodeURIComponent(state.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n getCacheKey(state, chain, fields) {\n // This is not a region-based source; it doesn't make sense to cache by a region\n return this.getURL(state, chain, fields);\n }\n}\n\n/**\n * Base class for \"connectors\"- this is a highly specialized kind of adapter that is rarely used in most LocusZoom\n * deployments. This is meant to be subclassed, rather than used directly.\n *\n * A connector is a data adapter that makes no server requests and caches no data of its own. Instead, it decides how to\n * combine data from other sources in the chain. Connectors are useful when we want to request (or calculate) some\n * useful piece of information once, but apply it to many different kinds of record types.\n *\n * Typically, a subclass will implement the field merging logic in `combineChainBody`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseAdapter\n */\nclass ConnectorSource extends BaseAdapter {\n /**\n * @param {Object} config.params Additional parameters\n * @param {Object} config.params.sources Specify how the hard-coded logic should find the data it relies on in the chain,\n * as {internal_name: chain_source_id} pairs. This allows writing a reusable connector that does not need to make\n * assumptions about what namespaces a source is using. *\n */\n constructor(config) {\n super(config);\n\n if (!config || !config.sources) {\n throw new Error('Connectors must specify the data they require as config.sources = {internal_name: chain_source_id}} pairs');\n }\n\n /**\n * Tells the connector how to find the data it relies on\n *\n * For example, a connector that applies burden test information to the genes layer might specify:\n * {gene_ns: \"gene\", aggregation_ns: \"aggregation\"}\n *\n * @member {Object}\n * @private\n */\n this._source_name_mapping = config.sources;\n\n // Validate that this source has been told how to find the required information\n const specified_ids = Object.keys(config.sources);\n /** @property {String[]} Specifies the sources that must be provided in the original config object */\n\n this._getRequiredSources().forEach((k) => {\n if (!specified_ids.includes(k)) {\n // TODO: Fix constructor.name usage in minified bundles\n throw new Error(`Configuration for ${this.constructor.name} must specify a source ID corresponding to ${k}`);\n }\n });\n }\n\n // Stub- connectors don't have their own url or data, so the defaults don't make sense\n parseInit() {}\n\n getRequest(state, chain, fields) {\n // Connectors do not request their own data by definition, but they *do* depend on other sources having been loaded\n // first. This method performs basic validation, and preserves the accumulated body from the chain so far.\n Object.keys(this._source_name_mapping).forEach((ns) => {\n const chain_source_id = this._source_name_mapping[ns];\n if (chain.discrete && !chain.discrete[chain_source_id]) {\n throw new Error(`${this.constructor.name} cannot be used before loading required data for: ${chain_source_id}`);\n }\n });\n return Promise.resolve(chain.body || []);\n }\n\n parseResponse(data, chain, fields, outnames, trans) {\n // A connector source does not update chain.discrete, but it may use it. It bypasses data formatting\n // and field selection (both are assumed to have been done already, by the previous sources this draws from)\n\n // Because of how the chain works, connectors are not very good at applying new transformations or namespacing.\n // Typically connectors are called with `connector_name:all` in the fields array.\n return Promise.resolve(this.combineChainBody(data, chain, fields, outnames, trans))\n .then(function(new_body) {\n return {header: chain.header || {}, discrete: chain.discrete || {}, body: new_body};\n });\n }\n\n combineChainBody(records, chain) {\n // Stub method: specifies how to combine the data\n throw new Error('This method must be implemented in a subclass');\n }\n\n /**\n * Helper method since ES6 doesn't support class fields\n * @private\n */\n _getRequiredSources() {\n throw new Error('Must specify an array that identifes the kind of data required by this source');\n }\n}\n\nexport { BaseAdapter, BaseApiAdapter };\n\nexport {\n AssociationLZ,\n ConnectorSource,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import transforms from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n const parts = /^(?:([^:]+):)?([^:|]*)(\\|.+)*$/.exec(field);\n /** @member {String} */\n this.full_name = field;\n /** @member {String} */\n this.namespace = parts[1] || null;\n /** @member {String} */\n this.name = parts[2] || null;\n /** @member {Array} */\n this.transformations = [];\n\n if (typeof parts[3] == 'string' && parts[3].length > 1) {\n this.transformations = parts[3].substring(1).split('|');\n this.transformations.forEach((transform, i) => this.transformations[i] = transforms.get(transform));\n }\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (typeof (data[`${this.namespace}:${this.name}`]) != 'undefined') { // Fallback: value sans transforms\n val = data[`${this.namespace}:${this.name}`];\n } else if (typeof data[this.name] != 'undefined') { // Fallback: value present without namespace\n val = data[this.name];\n } else if (extra && typeof extra[this.full_name] != 'undefined') { // Fallback: check annotations\n val = extra[this.full_name];\n } // We should really warn if no value found, but many bad layouts exist and this could break compatibility\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply namespaces to layout, recursively\n * @private\n */\nfunction applyNamespaces(element, namespace, default_namespace) {\n if (namespace) {\n if (typeof namespace == 'string') {\n namespace = { default: namespace };\n }\n } else {\n namespace = { default: '' };\n }\n if (typeof element == 'string') {\n const re = /\\{\\{namespace(\\[[A-Za-z_0-9]+\\]|)\\}\\}/g;\n let match, base, key, resolved_namespace;\n const replace = [];\n while ((match = re.exec(element)) !== null) {\n base = match[0];\n key = match[1].length ? match[1].replace(/(\\[|\\])/g, '') : null;\n resolved_namespace = default_namespace;\n if (namespace != null && typeof namespace == 'object' && typeof namespace[key] != 'undefined') {\n resolved_namespace = namespace[key] + (namespace[key].length ? ':' : '');\n }\n replace.push({ base: base, namespace: resolved_namespace });\n }\n for (let r in replace) {\n element = element.replace(replace[r].base, replace[r].namespace);\n }\n } else if (typeof element == 'object' && element != null) {\n if (typeof element.namespace != 'undefined') {\n const merge_namespace = (typeof element.namespace == 'string') ? { default: element.namespace } : element.namespace;\n namespace = merge(namespace, merge_namespace);\n }\n let namespaced_element, namespaced_property;\n for (let property in element) {\n if (property === 'namespace') {\n continue;\n }\n namespaced_element = applyNamespaces(element[property], namespace, default_namespace);\n namespaced_property = applyNamespaces(property, namespace, default_namespace);\n if (property !== namespaced_property) {\n delete element[property];\n }\n element[namespaced_property] = namespaced_element;\n }\n }\n return element;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {}\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, renameField };\n","import { TRANSFORMS } from '../registry';\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes state information and ensures that data is formatted in the manner expected by the plot.\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * It is also responsible for constructing a \"chain\" of dependent requests, by requesting each datasource\n * sequentially in the order specified in the datalayer `fields` array. Data sources are only chained within a\n * data layer, and only if that layer requests more than one source of data.\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n __split_requests(fields) {\n // Given a fields array, return an object specifying what datasource names the data layer should make requests\n // to, and how to handle the returned data\n var requests = {};\n // Regular expression finds namespace:field|trans\n var re = /^(?:([^:]+):)?([^:|]*)(\\|.+)*$/;\n fields.forEach(function(raw) {\n var parts = re.exec(raw);\n var ns = parts[1] || 'base';\n var field = parts[2];\n var trans = TRANSFORMS.get(parts[3]);\n if (typeof requests[ns] == 'undefined') {\n requests[ns] = {outnames:[], fields:[], trans:[]};\n }\n requests[ns].outnames.push(raw);\n requests[ns].fields.push(field);\n requests[ns].trans.push(trans);\n });\n return requests;\n }\n\n /**\n * Fetch data, and create a chain that only connects two data sources if they depend on each other\n * @param {Object} state The current \"state\" of the plot, such as chromosome and start/end positions\n * @param {String[]} fields The list of data fields specified in the `layout` for a specific data layer\n * @returns {Promise}\n */\n getData(state, fields) {\n var requests = this.__split_requests(fields);\n // Create an array of functions that, when called, will trigger the request to the specified datasource\n var request_handles = Object.keys(requests).map((key) => {\n if (!this._sources.get(key)) {\n throw new Error(`Datasource for namespace ${key} not found`);\n }\n return this._sources.get(key).getData(\n state,\n requests[key].fields,\n requests[key].outnames,\n requests[key].trans\n );\n });\n //assume the fields are requested in dependent order\n //TODO: better manage dependencies\n var ret = Promise.resolve({header:{}, body: [], discrete: {}});\n for (var i = 0; i < request_handles.length; i++) {\n // If a single datalayer uses multiple sources, perform the next request when the previous one completes\n ret = ret.then(request_handles[i]);\n }\n return ret;\n }\n}\n\n\nexport default Requester;\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot.panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import widgets from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n // In LZ 0.12, `dashboard.components` was renamed to `toolbar.widgets`. Preserve a backwards-compatible alias.\n const options = (this.parent.layout.dashboard && this.parent.layout.dashboard.components) || this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n try {\n const widget = widgets.create(layout.type, layout, this);\n this.widgets.push(widget);\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot.panel_boundaries.dragging || this.parent_plot.interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 12,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent.data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n if (Array.isArray(this.parent.data_layers[id].layout.legend)) {\n this.parent.data_layers[id].layout.legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size || 12;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all panels in the plot\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset]\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n // Ensure a valid ID is present. If there is no valid ID then generate one\n if (typeof layout.id !== 'string' || !layout.id.length) {\n if (!this.parent) {\n layout.id = `p${Math.floor(Math.random() * Math.pow(10, 8))}`;\n } else {\n const generateID = () => {\n let id = `p${Math.floor(Math.random() * Math.pow(10, 8))}`;\n if (id === null || typeof this.parent.panels[id] != 'undefined') {\n id = generateID();\n }\n return id;\n };\n layout.id = generateID();\n }\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this.initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this.layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this.state_id = this.id;\n this.state[this.state_id] = this.state[this.state_id] || {};\n } else {\n this.state = null;\n this.state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this.data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this.data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this.zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this.event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this.event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this.event_hooks[event] = [];\n }\n this.event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this.event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this.event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this.event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this.event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id [${layout.id}]; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this.data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this.data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this.data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this.data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this.data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id].layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n if (!this.data_layers[id]) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n this.data_layers[id].destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (this.data_layers[id].svg.container) {\n this.data_layers[id].svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(this.data_layers[id].layout_idx, 1);\n delete this.state[this.data_layers[id].state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this.data_layer_ids_by_z_index.splice(this.data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id].layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this.data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Set and position the inner border, style if necessary\n this.inner_border\n .attr('x', this.layout.margin.left)\n .attr('y', this.layout.margin.top)\n .attr('width', this.parent_plot.layout.width - (this.layout.margin.left + this.layout.margin.right))\n .attr('height', this.layout.height - (this.layout.margin.top + this.layout.margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (this.layout.axes.x.range) {\n base_x_range.start = this.layout.axes.x.range.start || base_x_range.start;\n base_x_range.end = this.layout.axes.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: this.layout.cliparea.height, end: 0 };\n if (this.layout.axes.y1.range) {\n base_y1_range.start = this.layout.axes.y1.range.start || base_y1_range.start;\n base_y1_range.end = this.layout.axes.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: this.layout.cliparea.height, end: 0 };\n if (this.layout.axes.y2.range) {\n base_y2_range.start = this.layout.axes.y2.range.start || base_y2_range.start;\n base_y2_range.end = this.layout.axes.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n if (this.parent.interaction.panel_id && (this.parent.interaction.panel_id === this.id || this.parent.interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (this.parent.interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = this.parent.interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = this.parent.interaction.zooming.center - this.layout.margin.left - this.layout.origin.x;\n const offset_ratio = anchor / this.layout.cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (this.parent.interaction.dragging) {\n switch (this.parent.interaction.dragging.method) {\n case 'background':\n ranges.x_shifted[0] = +this.parent.interaction.dragging.dragged_x;\n ranges.x_shifted[1] = this.layout.cliparea.width + this.parent.interaction.dragging.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +this.parent.interaction.dragging.dragged_x;\n ranges.x_shifted[1] = this.layout.cliparea.width + this.parent.interaction.dragging.dragged_x;\n } else {\n anchor = this.parent.interaction.dragging.start_x - this.layout.margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + this.parent.interaction.dragging.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(this.layout.cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${this.parent.interaction.dragging.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = this.layout.cliparea.height + this.parent.interaction.dragging.dragged_y;\n ranges[y_shifted][1] = +this.parent.interaction.dragging.dragged_y;\n } else {\n anchor = this.layout.cliparea.height - (this.parent.interaction.dragging.start_y - this.layout.margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - this.parent.interaction.dragging.dragged_y), 3);\n ranges[y_shifted][0] = this.layout.cliparea.height;\n ranges[y_shifted][1] = this.layout.cliparea.height - (this.layout.cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent.interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n this.parent.interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this.zoom_timeout !== null) {\n clearTimeout(this.zoom_timeout);\n }\n this.zoom_timeout = setTimeout(() => {\n this.parent.interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this.data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this.initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this.data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!Object.keys(this.layout.axes[axis]).length || this.layout.axes[axis].render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n this.layout.axes[axis].render = false;\n } else {\n this.layout.axes[axis].render = true;\n this.layout.axes[axis].label = this.layout.axes[axis].label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n this.layout.height = Math.max(Math.round(+height), this.layout.min_height);\n }\n }\n this.layout.cliparea.width = Math.max(this.parent_plot.layout.width - (this.layout.margin.left + this.layout.margin.right), 0);\n this.layout.cliparea.height = Math.max(this.layout.height - (this.layout.margin.top + this.layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', this.layout.height);\n }\n if (this.initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this.initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n if (!isNaN(top) && top >= 0) {\n this.layout.margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n this.layout.margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n this.layout.margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n this.layout.margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (this.layout.margin.top + this.layout.margin.bottom > this.layout.height) {\n extra = Math.floor(((this.layout.margin.top + this.layout.margin.bottom) - this.layout.height) / 2);\n this.layout.margin.top -= extra;\n this.layout.margin.bottom -= extra;\n }\n if (this.layout.margin.left + this.layout.margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((this.layout.margin.left + this.layout.margin.right) - this.parent_plot.layout.width) / 2);\n this.layout.margin.left -= extra;\n this.layout.margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n this.layout.margin[m] = Math.max(this.layout.margin[m], 0);\n });\n this.layout.cliparea.width = Math.max(this.parent_plot.layout.width - (this.layout.margin.left + this.layout.margin.right), 0);\n this.layout.cliparea.height = Math.max(this.layout.height - (this.layout.margin.top + this.layout.margin.bottom), 0);\n this.layout.cliparea.origin.x = this.layout.margin.left;\n this.layout.cliparea.origin.y = this.layout.margin.top;\n\n if (this.initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this.data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this.data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent.panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n if (this.parent.panel_ids_by_y_index[this.layout.y_index - 1]) {\n this.parent.panel_ids_by_y_index[this.layout.y_index] = this.parent.panel_ids_by_y_index[this.layout.y_index - 1];\n this.parent.panel_ids_by_y_index[this.layout.y_index - 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n if (this.parent.panel_ids_by_y_index[this.layout.y_index + 1]) {\n this.parent.panel_ids_by_y_index[this.layout.y_index] = this.parent.panel_ids_by_y_index[this.layout.y_index + 1];\n this.parent.panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this.data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this.data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this.data_promises)\n .then(() => {\n this.initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this.data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this.data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this.data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([A-Za-z0-9_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this.initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this.panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this.remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this.event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this.interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this.event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this.event_hooks[event] = [];\n }\n this.event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this.event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this.event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this.event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this.event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this.panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this.panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this.panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this.panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id].layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this.initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid].data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer.layer_state;\n delete this.layout.state[layer.state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n if (!this.panels[id]) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this.panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n this.panels[id].loader.hide();\n this.panels[id].toolbar.destroy(true);\n this.panels[id].curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (this.panels[id].svg.container) {\n this.panels[id].svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(this.panels[id].layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id].layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this.panel_ids_by_y_index.splice(this.panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this.initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @param {String[]} fields An array of field names and transforms, in the same syntax used by a data layer.\n * Different data sources should be prefixed by the namespace name.\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @param {Object} [opts] Options\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {boolean} [opts.discrete=false] Normally the callback will subscribe to the combined body from the chain,\n * which may not be in a format that matches what the external callback wants to do. If discrete=true, returns the\n * uncombined record info\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(fields, success_callback, opts) {\n opts = opts || {};\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = opts.onerror || function (err) {\n console.log('An error occurred while acting on an external callback', err);\n };\n\n const listener = () => {\n try {\n this.lzd.getData(this.state, fields)\n .then((new_data) => success_callback(opts.discrete ? new_data.discrete : new_data.body, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this.remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this.remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this.remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this.panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel.data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this.initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n if (panel_id) {\n return ((typeof this.interaction.panel_id == 'undefined' || this.interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(this.interaction.dragging || this.interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this.panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n if (this.layout.responsive_resize) {\n const resize_listener = () => this.rescaleSVG();\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this.panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this.initialized) {\n this.panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let id in this.panels) {\n if (this.panels[id].layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, this.panels[id].layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, this.panels[id].layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this.panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel.layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel.layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel.layout.margin.right, 0);\n panel.layout.width += delta;\n panel.layout.margin.left = x_linked_margins.left;\n panel.layout.margin.right = x_linked_margins.right;\n panel.layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this.panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this.mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this.panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent.panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent.panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent.panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent.panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent.panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this.panel_boundaries.hide_timeout);\n this.panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this.panel_boundaries.hide_timeout = setTimeout(() => {\n this.panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this.mouse_guide.vertical.attr('x', -1);\n this.mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this.mouse_guide.vertical.attr('x', coords[0]);\n this.mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n if (this.interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n this.interaction.dragging.dragged_x = coords[0] - this.interaction.dragging.start_x;\n this.interaction.dragging.dragged_y = coords[1] - this.interaction.dragging.start_y;\n this.panels[this.interaction.panel_id].render();\n this.interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this.initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this.interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n\n if (!this.interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[this.interaction.panel_id] != 'object') {\n this.interaction = {};\n return this;\n }\n const panel = this.panels[this.interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel.data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (this.interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (this.interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (this.interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(this.interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this.interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} input value\n */\nconst if_value = (parameters, input) => {\n if (typeof input == 'undefined' || parameters.field_value !== input) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} input value\n * @returns {*}\n */\nconst numerical_bin = (parameters, input) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof input == 'undefined' || input === null || isNaN(+input)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+input < prev || (+input >= prev && +input < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, input) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof input == 'undefined' || input === null || isNaN(+input)) {\n return nullval;\n }\n if (+input <= parameters.breaks[0]) {\n return values[0];\n } else if (+input >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +input && breaks[idx] >= +input) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+input - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or line for a path.\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\".\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n * @type {{type: string, fields: Array, x_axis: {}, y_axis: {}}}\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n fields: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {String[]} layout.fields A list of (namespaced) fields specifying what data is used by the layer. Only\n * these fields will be made available to the data layer, and only data sources (namespaces) referred to in\n * this array will be fetched. This represents the \"contract\" between what data is returned and what data is rendered.\n * This fields array works in concert with the data retrieval method BaseAdapter.extractFields.\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum.\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this.initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this.layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this.state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this.layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this.tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this.global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n }\n\n /****** Public interface: methods for external manipulation */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n if (this.parent.data_layer_ids_by_z_index[this.layout.z_index + 1]) {\n this.parent.data_layer_ids_by_z_index[this.layout.z_index] = this.parent.data_layer_ids_by_z_index[this.layout.z_index + 1];\n this.parent.data_layer_ids_by_z_index[this.layout.z_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n if (this.parent.data_layer_ids_by_z_index[this.layout.z_index - 1]) {\n this.parent.data_layer_ids_by_z_index[this.layout.z_index] = this.parent.data_layer_ids_by_z_index[this.layout.z_index - 1];\n this.parent.data_layer_ids_by_z_index[this.layout.z_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this.layer_state.extra_fields[id]) {\n this.layer_state.extra_fields[id] = {};\n }\n this.layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n * @protected\n * @param {Object} element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n const id_field = this.layout.id_field || 'id';\n if (typeof element[id_field] == 'undefined') {\n throw new Error('Unable to generate element ID');\n }\n const element_id = element[id_field].toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be separate visual nodes to show select/highlight statuses, or\n * even a common/shared node to show status across many elements in a set.\n * Abstract method. It should be overridden by data layers that implement seperate status\n * nodes specifically to the use case of the data layer type.\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = (match_function(field_resolver.resolve(item), broadcast_value));\n }\n\n item.toHTML = () => {\n const id_field = this.layout.id_field || 'id';\n let html = '';\n if (item[id_field]) {\n html = item[id_field].toString();\n }\n return html;\n };\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n // deselect() method - shortcut method to deselect the element\n item.deselect = () => {\n const data_layer = this.getDataLayer();\n data_layer.unselectElement(this); // dynamically generated method name. It exists, honest.\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this.layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this.state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this.state_id] = layer_state;\n }\n this.layer_state = layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this.tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this.tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this.layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this.tooltips[id].selector.html('');\n this.tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this.tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this.tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this.tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this.tooltips[id]) {\n if (typeof this.tooltips[id].selector == 'object') {\n this.tooltips[id].selector.remove();\n }\n delete this.tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this.layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this.tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this.tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this.tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this.tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n if (typeof this.layout.tooltip != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof this.layout.tooltip.show == 'string') {\n show_directive = { and: [ this.layout.tooltip.show ] };\n } else if (typeof this.layout.tooltip.show == 'object') {\n show_directive = this.layout.tooltip.show;\n }\n\n let hide_directive = {};\n if (typeof this.layout.tooltip.hide == 'string') {\n hide_directive = { and: [ this.layout.tooltip.hide ] };\n } else if (typeof this.layout.tooltip.hide == 'object') {\n hide_directive = this.layout.tooltip.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const layer_state = this.layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this.layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this.layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this.layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this.layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this.layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this.layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this.global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self.layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this.layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self.state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this.layout.fields)\n .then((new_data) => {\n this.data = new_data.body; // chain.body from datasources\n this.applyDataMethods();\n this.initialized = true;\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions && layout.regions.length && layout.fields && layout.fields.length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 12,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this.layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this.global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this.global_statuses).forEach((global_status) => {\n if (this.global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n\n // Vars for storing the data generated line\n /** @member {Array} */\n this.data = [];\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer.label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer.label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer.label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer.label_lines.nodes()[i]) : null;\n data_layer.label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this.seperate_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer.label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer.label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer.label_texts.nodes();\n data_layer.label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this.seperate_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this.label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this.label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this.label_texts) {\n this.label_texts.remove();\n }\n\n this.label_texts = this.label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this.label_lines) {\n this.label_lines.remove();\n }\n this.label_lines = this.label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this.label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this.label_texts) {\n this.label_texts.remove();\n }\n if (this.label_lines) {\n this.label_lines.remove();\n }\n if (this.label_groups) {\n this.label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this.seperate_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n namespace: { 'assoc': 'assoc' },\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{{{namespace[assoc]}}variant|htmlescape}}
\n P Value: {{{{namespace[assoc]}}log_pvalue|logtoscinotation|htmlescape}}
\n Ref. Allele: {{{{namespace[assoc]}}ref_allele|htmlescape}}
\n {{#if {{namespace[ld]}}isrefvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
`,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

{{gene_name|htmlescape}}

'\n + 'Gene ID: {{gene_id|htmlescape}}
'\n + 'Transcript ID: {{transcript_id|htmlescape}}
'\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
ConstraintExpected variantsObserved variantsConst. Metric
Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

{{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{{{namespace[catalog]}}variant|htmlescape}}
'\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
'\n + 'Top Trait: {{{{namespace[catalog]}}trait|htmlescape}}
'\n + 'Top P Value: {{{{namespace[catalog]}}log_pvalue|logtoscinotation}}
'\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n namespace: { 'access': 'access' },\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
' +\n '{{{{namespace[access]}}start1|htmlescape}}-{{{{namespace[access]}}end1|htmlescape}}
' +\n 'Promoter
' +\n '{{{{namespace[access]}}start2|htmlescape}}-{{{{namespace[access]}}end2|htmlescape}}
' +\n '{{#if {{namespace[access]}}target}}Target: {{{{namespace[access]}}target|htmlescape}}
{{/if}}' +\n 'Score: {{{{namespace[access]}}score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n namespace: { 'recomb': 'recomb' },\n id: 'recombrate',\n type: 'line',\n tag: 'recombination',\n fields: ['{{namespace[recomb]}}position', '{{namespace[recomb]}}recomb_rate'],\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: '{{namespace[recomb]}}position',\n },\n y_axis: {\n axis: 2,\n field: '{{namespace[recomb]}}recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n fields: ['{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', '{{namespace[assoc]}}log_pvalue', '{{namespace[assoc]}}log_pvalue|logtoscinotation', '{{namespace[assoc]}}ref_allele', '{{namespace[ld]}}state', '{{namespace[ld]}}isrefvar'],\n id_field: '{{namespace[assoc]}}variant',\n coalesce: {\n active: true,\n },\n point_shape: {\n scale_function: 'if',\n field: '{{namespace[ld]}}isrefvar',\n parameters: {\n field_value: 1,\n then: 'diamond',\n else: 'circle',\n },\n },\n point_size: {\n scale_function: 'if',\n field: '{{namespace[ld]}}isrefvar',\n parameters: {\n field_value: 1,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: '{{namespace[ld]}}isrefvar',\n parameters: {\n field_value: 1,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: '{{namespace[ld]}}state',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { shape: 'diamond', color: '#9632b8', size: 40, label: 'LD Ref Var', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(219, 61, 17)', size: 40, label: '1.0 > r² ≥ 0.8', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(248, 195, 42)', size: 40, label: '0.8 > r² ≥ 0.6', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(110, 254, 104)', size: 40, label: '0.6 > r² ≥ 0.4', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(38, 188, 225)', size: 40, label: '0.4 > r² ≥ 0.2', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(70, 54, 153)', size: 40, label: '0.2 > r² ≥ 0.0', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#AAAAAA', size: 40, label: 'no r² data', class: 'lz-data_layer-scatter' },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: '{{namespace[assoc]}}position',\n },\n y_axis: {\n axis: 1,\n field: '{{namespace[assoc]}}log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n namespace: { 'access': 'access' },\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n fields: ['{{namespace[access]}}start1', '{{namespace[access]}}end1', '{{namespace[access]}}start2', '{{namespace[access]}}end2', '{{namespace[access]}}id', '{{namespace[access]}}target', '{{namespace[access]}}score'],\n match: { send: '{{namespace[access]}}target', receive: '{{namespace[access]}}target' },\n id_field: '{{namespace[access]}}id',\n filters: [\n { field: '{{namespace[access]}}score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: '{{namespace[access]}}start1',\n field2: '{{namespace[access]}}start2',\n },\n y_axis: {\n axis: 1,\n field: '{{namespace[access]}}score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n base.tooltip.html += '{{#if {{namespace[catalog]}}rsid}}
See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n base.fields.push('{{namespace[catalog]}}rsid', '{{namespace[catalog]}}trait', '{{namespace[catalog]}}log_pvalue');\n return base;\n}();\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n namespace: { 'phewas': 'phewas' },\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n point_shape: 'circle',\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{namespace[phewas]}}id',\n fields: ['{{namespace[phewas]}}id', '{{namespace[phewas]}}log_pvalue', '{{namespace[phewas]}}trait_group', '{{namespace[phewas]}}trait_label'],\n x_axis: {\n field: '{{namespace[phewas]}}x', // Synthetic/derived field added by `category_scatter` layer\n category_field: '{{namespace[phewas]}}trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: '{{namespace[phewas]}}log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: '{{namespace[phewas]}}trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: [\n 'Trait: {{{{namespace[phewas]}}trait_label|htmlescape}}
',\n 'Trait Category: {{{{namespace[phewas]}}trait_group|htmlescape}}
',\n 'P-value: {{{{namespace[phewas]}}log_pvalue|logtoscinotation|htmlescape}}
',\n ].join(''),\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{{{namespace[phewas]}}trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: '{{namespace[phewas]}}log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n fields: ['{{namespace[gene]}}all', '{{namespace[constraint]}}all'],\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: '{{namespace[assoc]}}variant',\n x_axis: {\n field: '{{namespace[assoc]}}position',\n },\n color: '#0000CC',\n fields: [\n '{{namespace[assoc]}}variant', '{{namespace[assoc]}}chromosome', '{{namespace[assoc]}}position',\n '{{namespace[catalog]}}variant', '{{namespace[catalog]}}rsid', '{{namespace[catalog]}}trait',\n '{{namespace[catalog]}}log_pvalue', '{{namespace[catalog]}}pos',\n ],\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: '{{namespace[catalog]}}rsid', operator: '!=', value: null },\n { field: '{{namespace[catalog]}}log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n }\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 225,\n margin: { top: 35, right: 50, bottom: 40, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 28,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 40,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 55, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 50, bottom: 40, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 28,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n namespace: { 'assoc': 'assoc', 'ld': 'ld', 'catalog': 'catalog' }, // Required to resolve display options\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{{{namespace[catalog]}}trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: '{{namespace[catalog]}}trait', operator: '!=', value: null },\n { field: '{{namespace[catalog]}}log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: '{{namespace[ld]}}state', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '10px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 50, bottom: 20, left: 50 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu)\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 50, bottom: 120, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 28,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 50, bottom: 10, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel)\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or using namespaces to convert an abstract layout into a concrete one.\n let base = super.get(type).get(name);\n base = merge(overrides, base);\n if (base.unnamespaced) {\n delete base.unnamespaced;\n return deepCopy(base);\n }\n let default_namespace = '';\n if (typeof base.namespace == 'string') {\n default_namespace = base.namespace;\n } else if (typeof base.namespace == 'object' && Object.keys(base.namespace).length) {\n if (typeof base.namespace.default != 'undefined') {\n default_namespace = base.namespace.default;\n } else {\n default_namespace = base.namespace[Object.keys(base.namespace)[0]].toString();\n }\n }\n default_namespace += default_namespace.length ? ':' : '';\n const result = applyNamespaces(base, base.namespace, default_namespace);\n\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n WIDGETS as Widgets,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/./node_modules/@hapi/hoek/lib/assert.js","webpack://[name]/./node_modules/@hapi/hoek/lib/error.js","webpack://[name]/./node_modules/@hapi/hoek/lib/stringify.js","webpack://[name]/./node_modules/@hapi/topo/lib/index.js","webpack://[name]/./node_modules/just-clone/index.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./node_modules/undercomplicate/esm/lru_cache.js","webpack://[name]/./node_modules/undercomplicate/esm/util.js","webpack://[name]/./node_modules/undercomplicate/esm/requests.js","webpack://[name]/./node_modules/undercomplicate/esm/joins.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./node_modules/undercomplicate/esm/adapter.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/registry/data_ops.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["AssertError","module","exports","condition","args","length","Error","Stringify","super","filter","arg","map","message","join","captureStackTrace","this","assert","JSON","stringify","apply","err","Assert","internals","_items","nodes","options","before","concat","after","group","sort","includes","Array","isArray","node","item","seq","push","manual","valid","_sort","others","other","Object","assign","mergeSort","i","graph","graphAfters","create","groups","expandedGroups","graphNodeItem","ancestors","children","child","visited","sorted","next","j","shouldSeeCount","seenCount","k","seqIndex","value","sortedItem","a","b","getRegExpFlags","regExp","source","flags","global","ignoreCase","multiline","sticky","unicode","clone","obj","result","key","type","toString","call","slice","Date","getTime","RegExp","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","prop","prototype","hasOwnProperty","r","Symbol","toStringTag","RegistryBase","Map","name","has","override","set","delete","from","keys","ClassRegistry","parent_name","source_name","overrides","console","warn","arguments","base","sub","add","LLNode","metadata","prev","LRUCache","max_size","_max_size","_cur_size","_store","_head","_tail","cached","prior","_remove","old","callback","data","getLinkedData","shared_options","entities","dependencies","consolidate","parsed","spec","exec","name_alone","name_deps","deps","split","_parse_declaration","dag","toposort","entries","e","order","responses","provider","depends_on","this_result","Promise","all","then","prior_results","_provider_name","getData","values","all_results","groupBy","records","group_key","item_group","_any_match","left","right","left_key","right_key","right_index","results","left_match_value","right_matches","right_item","left_index","right_match_value","left_match","REGEX_MARKER","parseMarker","test","match","BaseApiAdapter","BaseLZAdapter","config","_config","cache_enabled","cache_size","_enable_cache","_cache","dependent_data","response_text","_buildRequestOptions","cache_key","_getCacheKey","resolve","_performRequest","text","_normalizeResponse","_cache_meta","catch","remove","_annotateRecords","_postProcessResponse","_url","url","_getURL","fetch","response","ok","statusText","parse","params","prefix_namespace","limit_fields","_prefix_namespace","_limit_fields","Set","chr","start","end","superset","find","md","row","reduce","acc","label","a_record","fieldname","suffixer","BaseUMAdapter","_genome_build","genome_build","build","constructor","N","every","fields","record","AssociationLZ","_source_id","request_options","GwasCatalogLZ","_validateBuildSource","source_query","GeneLZ","GeneConstraintLZ","state","genes_data","unique_gene_names","gene","gene_name","query","replace","body","method","headers","LDServer","assoc_data","assoc_variant_name","_findPrefixedKey","assoc_logp_name","refvar","best_hit","ldrefvar","best_logp","variant","log_pvalue","lz_is_ld_refvar","chrom","pos","ref","alt","coord","__find_ld_refvar","_skip_request","ld_refvar","ld_source","ld_population","ld_pop","population","encodeURIComponent","combined","chainRequests","payload","forEach","RecombLZ","StaticSource","_data","PheWASLZ","registry","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","transforms","full_name","field_name","transformations","val","transform","extra","undefined","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","m","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","paths","p","_","__","subject","uniqPaths","arr","elem","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","layout","shared_namespaces","requested_ns","merge","custom_layout","default_layout","property","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","findFields","prefixes","field_finder","all_ns","value_type","a_match","renameField","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","regex","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","DataOperation","join_type","initiator","_callable","_initiator","_params","plot_state","dependent_recordsets","data_layer","sources","_sources","namespace_options","data_operations","namespace_local_names","dependency_order","unshift","ns_pattern","local_name","global_name","dep_spec","requires","namecount","require_name","task","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","parentNode","insert","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","x","width","delay","setTimeout","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","position","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","index","indexOf","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","element","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","_panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","radioId","has_option","choice","defaultName","default_config_display_name","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","addWidget","widget","error","_panel_boundaries","dragging","_interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","line_height","_data_layer_ids_by_z_index","reverse","label_x","label_y","shape_factory","path_y","radius","PI","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","_initialized","_layout_idx","_state_id","_data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","_zoom_timeout","_event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","parseFloat","y_axis","axis","z_index","dlid","idx","layout_idx","data_layer_layout","target_layer","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","axes_config","base_x_range","range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","current_drag","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","scale","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","domain","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","namespace","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tickValues","tick_format","tickFormat","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","panel_boundaries","mouse_guide","Plot","datasource","_remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","_layer_state","_setDefaultState","target_panel","clearPanelData","opts","success_callback","from_layer","onerror","error_callback","base_prefix","layer_target","startsWith","is_valid_layer","some","listener","config_to_sources","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","mutateLayout","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","resize_listener","rescaleSVG","window","addEventListener","trackExternalListener","IntersectionObserver","threshold","observer","entry","intersectionRatio","observe","load_listener","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","_mouse_guide","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","ret","positionStringToInt","suffixre","mult","tokens","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","field_value","numerical_bin","breaks","null_value","curr","categorical_bin","categories","ordinal_cycle","stable_choice","max_cache_size","clear","hash","String","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","effect_direction","input","beta_field","stderr_beta_field","plus_result","neg_result","beta_val","se_val","id_field","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","_tooltips","_global_statuses","_data_contract","_entities","_dependencies","layer_order","current_index","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","fields_unseen","debug","lz_is_match","getDataLayer","getPanel","getPlot","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","tooltip_layout","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","_label_texts","da","dal","_label_lines","dax","abound","bbound","_label_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","_label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","version","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","custom_namespaces","_auto_fields","contents","list","_wrap_join","handle","catalog_data","assoc_key","catalog_key","catalog_logp_name","catalog_by_variant","catalog_flat","claims","best_variant","best","n_catalog_matches","constraint_data","alias","constraint","LocusZoom","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","source_id","Adapters","DataLayers","DataFunctions","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["'use strict';\n\nconst AssertError = require('./error');\n\nconst internals = {};\n\n\nmodule.exports = function (condition, ...args) {\n\n if (condition) {\n return;\n }\n\n if (args.length === 1 &&\n args[0] instanceof Error) {\n\n throw args[0];\n }\n\n throw new AssertError(args);\n};\n","'use strict';\n\nconst Stringify = require('./stringify');\n\n\nconst internals = {};\n\n\nmodule.exports = class extends Error {\n\n constructor(args) {\n\n const msgs = args\n .filter((arg) => arg !== '')\n .map((arg) => {\n\n return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);\n });\n\n super(msgs.join(' ') || 'Unknown error');\n\n if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$\n Error.captureStackTrace(this, exports.assert);\n }\n }\n};\n","'use strict';\n\nconst internals = {};\n\n\nmodule.exports = function (...args) {\n\n try {\n return JSON.stringify.apply(null, args);\n }\n catch (err) {\n return '[Cannot display object: ' + err.message + ']';\n }\n};\n","'use strict';\n\nconst Assert = require('@hapi/hoek/lib/assert');\n\n\nconst internals = {};\n\n\nexports.Sorter = class {\n\n constructor() {\n\n this._items = [];\n this.nodes = [];\n }\n\n add(nodes, options) {\n\n options = options || {};\n\n // Validate rules\n\n const before = [].concat(options.before || []);\n const after = [].concat(options.after || []);\n const group = options.group || '?';\n const sort = options.sort || 0; // Used for merging only\n\n Assert(!before.includes(group), `Item cannot come before itself: ${group}`);\n Assert(!before.includes('?'), 'Item cannot come before unassociated items');\n Assert(!after.includes(group), `Item cannot come after itself: ${group}`);\n Assert(!after.includes('?'), 'Item cannot come after unassociated items');\n\n if (!Array.isArray(nodes)) {\n nodes = [nodes];\n }\n\n for (const node of nodes) {\n const item = {\n seq: this._items.length,\n sort,\n before,\n after,\n group,\n node\n };\n\n this._items.push(item);\n }\n\n // Insert event\n\n if (!options.manual) {\n const valid = this._sort();\n Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');\n }\n\n return this.nodes;\n }\n\n merge(others) {\n\n if (!Array.isArray(others)) {\n others = [others];\n }\n\n for (const other of others) {\n if (other) {\n for (const item of other._items) {\n this._items.push(Object.assign({}, item)); // Shallow cloned\n }\n }\n }\n\n // Sort items\n\n this._items.sort(internals.mergeSort);\n for (let i = 0; i < this._items.length; ++i) {\n this._items[i].seq = i;\n }\n\n const valid = this._sort();\n Assert(valid, 'merge created a dependencies error');\n\n return this.nodes;\n }\n\n sort() {\n\n const valid = this._sort();\n Assert(valid, 'sort created a dependencies error');\n\n return this.nodes;\n }\n\n _sort() {\n\n // Construct graph\n\n const graph = {};\n const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives\n const groups = Object.create(null);\n\n for (const item of this._items) {\n const seq = item.seq; // Unique across all items\n const group = item.group;\n\n // Determine Groups\n\n groups[group] = groups[group] || [];\n groups[group].push(seq);\n\n // Build intermediary graph using 'before'\n\n graph[seq] = item.before;\n\n // Build second intermediary graph with 'after'\n\n for (const after of item.after) {\n graphAfters[after] = graphAfters[after] || [];\n graphAfters[after].push(seq);\n }\n }\n\n // Expand intermediary graph\n\n for (const node in graph) {\n const expandedGroups = [];\n\n for (const graphNodeItem in graph[node]) {\n const group = graph[node][graphNodeItem];\n groups[group] = groups[group] || [];\n expandedGroups.push(...groups[group]);\n }\n\n graph[node] = expandedGroups;\n }\n\n // Merge intermediary graph using graphAfters into final graph\n\n for (const group in graphAfters) {\n if (groups[group]) {\n for (const node of groups[group]) {\n graph[node].push(...graphAfters[group]);\n }\n }\n }\n\n // Compile ancestors\n\n const ancestors = {};\n for (const node in graph) {\n const children = graph[node];\n for (const child of children) {\n ancestors[child] = ancestors[child] || [];\n ancestors[child].push(node);\n }\n }\n\n // Topo sort\n\n const visited = {};\n const sorted = [];\n\n for (let i = 0; i < this._items.length; ++i) { // Looping through item.seq values out of order\n let next = i;\n\n if (ancestors[i]) {\n next = null;\n for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values\n if (visited[j] === true) {\n continue;\n }\n\n if (!ancestors[j]) {\n ancestors[j] = [];\n }\n\n const shouldSeeCount = ancestors[j].length;\n let seenCount = 0;\n for (let k = 0; k < shouldSeeCount; ++k) {\n if (visited[ancestors[j][k]]) {\n ++seenCount;\n }\n }\n\n if (seenCount === shouldSeeCount) {\n next = j;\n break;\n }\n }\n }\n\n if (next !== null) {\n visited[next] = true;\n sorted.push(next);\n }\n }\n\n if (sorted.length !== this._items.length) {\n return false;\n }\n\n const seqIndex = {};\n for (const item of this._items) {\n seqIndex[item.seq] = item;\n }\n\n this._items = [];\n this.nodes = [];\n\n for (const value of sorted) {\n const sortedItem = seqIndex[value];\n this.nodes.push(sortedItem.node);\n this._items.push(sortedItem);\n }\n\n return true;\n }\n};\n\n\ninternals.mergeSort = (a, b) => {\n\n return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);\n};\n","module.exports = clone;\n\n/*\n Deep clones all properties except functions\n\n var arr = [1, 2, 3];\n var subObj = {aa: 1};\n var obj = {a: 3, b: 5, c: arr, d: subObj};\n var objClone = clone(obj);\n arr.push(4);\n subObj.bb = 2;\n obj; // {a: 3, b: 5, c: [1, 2, 3, 4], d: {aa: 1}}\n objClone; // {a: 3, b: 5, c: [1, 2, 3], d: {aa: 1, bb: 2}}\n*/\n\nfunction clone(obj) {\n if (typeof obj == 'function') {\n return obj;\n }\n var result = Array.isArray(obj) ? [] : {};\n for (var key in obj) {\n // include prototype properties\n var value = obj[key];\n var type = {}.toString.call(value).slice(8, -1);\n if (type == 'Array' || type == 'Object') {\n result[key] = clone(value);\n } else if (type == 'Date') {\n result[key] = new Date(value.getTime());\n } else if (type == 'RegExp') {\n result[key] = RegExp(value.source, getRegExpFlags(value));\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction getRegExpFlags(regExp) {\n if (typeof regExp.source.flags == 'string') {\n return regExp.source.flags;\n } else {\n var flags = [];\n regExp.global && flags.push('g');\n regExp.ignoreCase && flags.push('i');\n regExp.multiline && flags.push('m');\n regExp.sticky && flags.push('y');\n regExp.unicode && flags.push('u');\n return flags.join('');\n }\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.14.0-beta.1';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","// Implement an LRU Cache\n\nclass LLNode {\n constructor(key, value, metadata = {}, prev = null, next = null) {\n this.key = key;\n this.value = value;\n this.metadata = metadata;\n this.prev = prev;\n this.next = next;\n }\n}\n\nclass LRUCache {\n constructor(max_size = 3) {\n this._max_size = max_size;\n this._cur_size = 0; // replace with map.size so we aren't managing manually?\n this._store = new Map();\n\n // Track LRU state\n this._head = null;\n this._tail = null;\n\n // Validate options\n if (max_size === null || max_size < 0) {\n throw new Error('Cache \"max_size\" must be >= 0');\n }\n }\n\n has(key) {\n // Check key membership without updating LRU\n return this._store.has(key);\n }\n\n get(key) {\n // Retrieve value from cache (if present) and update LRU cache accordingly\n const cached = this._store.get(key);\n if (!cached) {\n return null;\n }\n if (this._head !== cached) {\n // Rewrite the cached value to ensure it is head of the list\n this.add(key, cached.value);\n }\n return cached.value;\n }\n\n add(key, value, metadata = {}) {\n // Add an item. Forcibly replaces the existing cached value for the same key.\n if (this._max_size === 0) {\n // Don't cache items if cache has 0 size. Also prevent users from trying \"negative number for infinite cache\".\n return;\n }\n\n const prior = this._store.get(key);\n if (prior) {\n this._remove(prior);\n }\n\n const node = new LLNode(key, value, metadata, null, this._head);\n\n if (this._head) {\n this._head.prev = node;\n } else {\n this._tail = node;\n }\n\n this._head = node;\n this._store.set(key, node);\n\n if (this._max_size >= 0 && this._cur_size >= this._max_size) {\n const old = this._tail;\n this._tail = this._tail.prev;\n this._remove(old);\n }\n this._cur_size += 1;\n }\n\n\n // Cache manipulation methods\n clear() {\n this._head = null;\n this._tail = null;\n this._cur_size = 0;\n this._store = new Map();\n }\n\n // Public method, remove by key\n remove(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return false;\n }\n this._remove(cached);\n return true;\n }\n\n // Internal implementation, useful when working on list\n _remove(node) {\n if (node.prev !== null) {\n node.prev.next = node.next;\n } else {\n this._head = node.next;\n }\n\n if (node.next !== null) {\n node.next.prev = node.prev;\n } else {\n this._tail = node.prev;\n }\n this._store.delete(node.key);\n this._cur_size -= 1;\n }\n\n /**\n * Find a matching item in the cache, or return null. This is useful for \"approximate match\" semantics,\n * to check if something qualifies as a cache hit despite not having the exact same key.\n * (Example: zooming into a region, where the smaller region is a subset of something already cached)\n * @param callback\n * @returns {null|LLNode}\n */\n find(callback) {\n let node = this._head;\n while (node) {\n const next = node.next;\n if (callback(node)) {\n return node;\n }\n node = next;\n }\n }\n}\n\nexport { LRUCache };\n","import justclone from 'just-clone';\n\n/**\n * The \"just-clone\" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.\n * @param data\n * @returns {*}\n */\nfunction clone(data) {\n if (typeof data !== 'object') {\n return data;\n }\n return justclone(data);\n}\n\nexport { clone };\n","/**\n * Perform a series of requests, respecting order of operations\n */\n\nimport {Sorter} from '@hapi/topo';\n\n\nfunction _parse_declaration(spec) {\n // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.\n const parsed = /^(?\\w+)$|((?\\w+)+\\(\\s*(?[^)]+?)\\s*\\))/.exec(spec);\n if (!parsed) {\n throw new Error(`Unable to parse dependency specification: ${spec}`);\n }\n\n let {name_alone, name_deps, deps} = parsed.groups;\n if (name_alone) {\n return [name_alone, []];\n }\n\n deps = deps.split(/\\s*,\\s*/);\n return [name_deps, deps];\n}\n\nfunction getLinkedData(shared_options, entities, dependencies, consolidate = true) {\n if (!dependencies.length) {\n return [];\n }\n\n const parsed = dependencies.map((spec) => _parse_declaration(spec));\n const dag = new Map(parsed);\n\n // Define the order to perform requests in, based on a DAG\n const toposort = new Sorter();\n for (let [name, deps] of dag.entries()) {\n try {\n toposort.add(name, {after: deps, group: name});\n } catch (e) {\n throw new Error(`Invalid or possible circular dependency specification for: ${name}`);\n }\n }\n const order = toposort.nodes;\n\n // Verify that all requested entities exist by name!\n const responses = new Map();\n for (let name of order) {\n const provider = entities.get(name);\n if (!provider) {\n throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);\n }\n\n // Each promise should only be triggered when the things it depends on have been resolved\n const depends_on = dag.get(name) || [];\n const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));\n\n const this_result = prereq_promises.then((prior_results) => {\n // Each request will be told the name of the provider that requested it. This can be used during post-processing,\n // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)\n // This has a secondary effect: it ensures that any changes made to \"shared\" options in one adapter will\n // not leak out to others via a mutable shared object reference.\n const options = Object.assign({_provider_name: name}, shared_options);\n return provider.getData(options, ...prior_results);\n });\n responses.set(name, this_result);\n }\n return Promise.all([...responses.values()])\n .then((all_results) => {\n if (consolidate) {\n // Some usages- eg fetch + data join tasks- will only require the last response in the sequence\n // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)\n return all_results[all_results.length - 1];\n }\n return all_results;\n });\n}\n\n\nexport {getLinkedData};\n\n// For testing only\nexport {_parse_declaration};\n","/**\n * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.\n */\nimport { clone } from './util';\n\n\nfunction groupBy(records, group_key) {\n const result = new Map();\n for (let item of records) {\n const item_group = item[group_key];\n\n if (typeof item_group === 'undefined') {\n throw new Error(`All records must specify a value for the field \"${group_key}\"`);\n }\n if (typeof item_group === 'object') {\n // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)\n throw new Error('Attempted to group on a field with non-primitive values');\n }\n\n let group = result.get(item_group);\n if (!group) {\n group = [];\n result.set(item_group, group);\n }\n group.push(item);\n }\n return result;\n}\n\n\nfunction _any_match(type, left, right, left_key, right_key) {\n // Helper that consolidates logic for all three join types\n const right_index = groupBy(right, right_key);\n const results = [];\n for (let item of left) {\n const left_match_value = item[left_key];\n const right_matches = right_index.get(left_match_value) || [];\n if (right_matches.length) {\n // Record appears on both left and right; equiv to an inner join\n results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));\n } else if (type !== 'inner') {\n // Record appears on left but not right\n results.push(clone(item));\n }\n }\n\n if (type === 'outer') {\n // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side\n const left_index = groupBy(left, left_key);\n for (let item of right) {\n const right_match_value = item[right_key];\n const left_matches = left_index.get(right_match_value) || [];\n if (!left_matches.length) {\n results.push(clone(item));\n }\n }\n }\n return results;\n}\n\n/**\n * Equivalent to LEFT OUTER JOIN in SQL.\n * @param left\n * @param right\n * @param left_key\n * @param right_key\n * @returns {*[]}\n */\nfunction left_match(left, right, left_key, right_key) {\n return _any_match('left', ...arguments);\n}\n\nfunction inner_match(left, right, left_key, right_key) {\n return _any_match('inner', ...arguments);\n}\n\nfunction full_outer_match(left, right, left_key, right_key) {\n return _any_match('outer', ...arguments);\n}\n\nexport {left_match, inner_match, full_outer_match, groupBy};\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 1 }]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 2 }]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nimport {BaseUrlAdapter} from 'undercomplicate';\n\nimport {parseMarker} from '../helpers/parse';\n\n// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs.\n// Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal\n// methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the\n// private API methods exist in the base class.\n\n/**\n * Replaced with the BaseLZAdapter class.\n * @public\n * @deprecated\n */\nclass BaseAdapter {\n constructor() {\n throw new Error('The \"BaseAdapter\" and \"BaseApiAdapter\" classes have been replaced in LocusZoom 0.14. See migration guide for details.');\n }\n}\n\n/**\n * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @deprecated\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {}\n\n\n/**\n * @param {object} config\n * @param [config.cache_enabled=true]\n * @param [config.cache_size=3]\n * @param [config.url]\n * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name.\n * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant)\n * Typically, this is only disabled if the response payload is very unusual\n * @param {String[]} [limit_fields=null] If an API returns far more data than is needed, this can be used to simplify\n * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.\n */\nclass BaseLZAdapter extends BaseUrlAdapter {\n constructor(config = {}) {\n if (config.params) {\n // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places.\n console.warn('Deprecation warning: all options in \"config.params\" should now be specified as top level keys.');\n Object.assign(config, config.params || {});\n delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality!\n }\n super(config);\n\n // Prefix the namespace for this source to all fieldnames: id -> assoc.id\n // This is useful for almost all layers because the layout object says where to find every field, exactly.\n // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on\n // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear\n // in the response. (gene_name instead of genes.gene_name)\n const { prefix_namespace = true, limit_fields } = config;\n this._prefix_namespace = prefix_namespace;\n this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields\n }\n\n /**\n * Determine how a particular request will be identified in cache. Most LZ requests are region based,\n * so the default is a string concatenation of `chr_start_end`\n * @param options Receives plot.state plus any other request options defined by this source\n * @returns {string}\n * @private\n */\n _getCacheKey(options) {\n // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default\n let {chr, start, end} = options; // Current view: plot.state\n\n // Does a prior cache hit qualify as a superset of the ROI?\n const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end);\n if (superset) {\n ({ chr, start, end } = superset.metadata);\n }\n\n // The default cache key is region-based, and this method only returns the region-based part of the cache hit\n // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check\n options._cache_meta = { chr, start, end };\n return `${chr}_${start}_${end}`;\n }\n\n /**\n * Add the \"local namespace\" as a prefix for every field returned for this request. Eg if the association api\n * returns a field called variant, and the source is referred to as \"assoc\" within a particular data layer, then\n * the returned records will have a field called \"assoc:variant\"\n *\n * @param records\n * @param options\n * @returns {*}\n * @private\n */\n _postProcessResponse(records, options) {\n if (!this._prefix_namespace || !Array.isArray(records)) {\n return records;\n }\n\n // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for\n // assoc data might see \"variant\" as \"assoc.variant\"\n const { _limit_fields } = this;\n const { _provider_name } = options;\n\n return records.map((row) => {\n return Object.entries(row).reduce(\n (acc, [label, value]) => {\n // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload.\n if (!_limit_fields || _limit_fields.has(label)) {\n acc[`${_provider_name}:${label}`] = value;\n }\n return acc;\n },\n {}\n );\n });\n }\n\n /**\n * Convenience method, manually called in LZ sources that deal with dependent data.\n *\n * In the last step of fetching data, LZ adds a prefix to each field name.\n * This means that operations like \"build query based on prior data\" can't just ask for \"log_pvalue\" because\n * they are receiving \"assoc:log_pvalue\" or some such unknown prefix.\n *\n * This helper lets us use dependent data more easily. Not every adapter needs to use this method.\n *\n * @param {Object} a_record One record (often the first one in a set of records)\n * @param {String} fieldname The desired fieldname, eg \"log_pvalue\"\n */\n _findPrefixedKey(a_record, fieldname) {\n const suffixer = new RegExp(`:${fieldname}$`);\n const match = Object.keys(a_record).find((key) => suffixer.test(key));\n if (!match) {\n throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`);\n }\n return match;\n }\n}\n\n\n/**\n * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies\n * of one particular web server.\n */\nclass BaseUMAdapter extends BaseLZAdapter {\n /**\n * @param {Object} config\n * @param {String} [config.build] The genome build to be used by all requests for this adapter.\n */\n constructor(config = {}) {\n super(config);\n // The UM portaldev API accepts an (optional) parameter \"genome_build\"\n this._genome_build = config.genome_build || config.build;\n }\n\n _validateBuildSource(build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${this.constructor.name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`);\n }\n }\n\n // Special behavior for the UM portaldev API: col -> row format normalization\n /**\n * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.\n * @param response_text\n * @param options\n * @returns {Object[]}\n * @private\n */\n _normalizeResponse(response_text, options) {\n let data = super._normalizeResponse(...arguments);\n // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload\n data = data.data || data;\n\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the columns, and create an object for each row record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n}\n\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n *\n * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL\n */\nclass AssociationLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files\n const { source } = config;\n this._source_id = source;\n }\n\n _getURL (request_options) {\n const {chr, start, end} = request_options;\n const base = super._getURL(request_options);\n return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`;\n }\n}\n\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GwasCatalogLZ extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build] The genome build to use when requesting the specific genomic region.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n const source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given.\n // We will avoid transforming or modifying the payload.\n this._prefix_namespace = false;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GeneConstraintLZ extends BaseLZAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config = {}) {\n super(config);\n this._prefix_namespace = false;\n }\n\n _buildRequestOptions(state, genes_data) {\n const build = state.genome_build || this._config.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = new Set();\n for (let gene of genes_data) {\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n unique_gene_names.add(gene.gene_name);\n }\n\n state.query = [...unique_gene_names.values()].map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n state.build = build;\n return Object.assign({}, state);\n }\n\n _performRequest(options) {\n let {query, build} = options;\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears to set max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve([]);\n }\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n\n const url = this._getURL(options);\n\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.\n */\n _normalizeResponse(response_text) {\n if (typeof response_text !== 'string') {\n // If the query short-circuits, we receive an empty list instead of a string\n return response_text;\n }\n const data = JSON.parse(response_text);\n return data.data;\n }\n}\n\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant and yse that as the LD reference variant.\n *\n * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly\n * if this information is not provided.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass LDServer extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.method='rsquare'] The metric used to calculate LD\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n __find_ld_refvar(state, assoc_data) {\n const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant');\n const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue');\n\n // Determine the reference variant (via user selected OR automatic-per-track)\n let refvar;\n let best_hit = {};\n if (state.ldrefvar) {\n // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data\n refvar = state.ldrefvar;\n best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {};\n } else {\n // find highest log-value and associated var spec\n let best_logp = 0;\n for (let item of assoc_data) {\n const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item;\n if (log_pvalue > best_logp) {\n best_logp = log_pvalue;\n refvar = variant;\n best_hit = item;\n }\n }\n }\n\n // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting.\n // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase.\n best_hit.lz_is_ld_refvar = true;\n\n // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server,\n // the variant fields must be normalized to a specific format. All later LD operations will use that format.\n const match = parseMarker(refvar, true);\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n\n const [chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip?\n if (ref && alt) {\n refvar += `_${ref}/${alt}`;\n }\n\n const coord = +pos;\n // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably\n // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby.\n if ((coord && state.ldrefvar && state.chr) && (chrom !== state.chr || coord < state.start || coord > state.end)) {\n // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a\n // *copy* of plot.state, so wiping here doesn't remove the original value.\n state.ldrefvar = null;\n return this.__find_ld_refvar(state, assoc_data);\n }\n\n // Return the reference variant, in a normalized format suitable for LDServer queries\n return refvar;\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n // No variants, so no need to annotate association data with LD!\n // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region)\n base._skip_request = true;\n return base;\n }\n\n base.ld_refvar = this.__find_ld_refvar(state, assoc_data);\n\n // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config\n const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let ld_source = state.ld_source || this._config.source || '1000G';\n const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL\n\n if (ld_source === '1000G' && genome_build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n ld_source = '1000G-FRZ09';\n }\n\n this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option\n return Object.assign({}, base, { genome_build, ld_source, ld_population });\n }\n\n _getURL(request_options) {\n const method = this._config.method || 'rsquare';\n const {\n chr, start, end,\n ld_refvar,\n genome_build, ld_source, ld_population,\n } = request_options;\n\n const base = super._getURL(request_options);\n\n return [\n base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(ld_refvar),\n '&chrom=', encodeURIComponent(chr),\n '&start=', encodeURIComponent(start),\n '&stop=', encodeURIComponent(end),\n ].join('');\n }\n\n _getCacheKey(options) {\n // LD is keyed by more than just region; append other parameters to the base cache key\n const base = super._getCacheKey(options);\n const { ld_refvar, ld_source, ld_population } = options;\n return `${base}_${ld_refvar}_${ld_source}_${ld_population}`;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird.\n return Promise.resolve([]);\n }\n\n const url = this._getURL(options);\n\n // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseUMAdapter {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['position', 'recomb_rate'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {object} config.data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseLZAdapter {\n constructor(config = {}) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n super(...arguments);\n const { data } = config;\n if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key\n throw new Error(\"'StaticSource' must provide data as required option 'config.data'\");\n }\n this._data = data;\n }\n\n _performRequest(options) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will\n * be used to provide PheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const base = super._getURL(request_options);\n const url = [\n base,\n \"?filter=variant eq '\", encodeURIComponent(request_options.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n _getCacheKey(options) {\n // Not a region based source; don't do anything smart for cache check\n return this._getURL(options);\n }\n}\n\n// Deprecated symbols\nexport { BaseAdapter, BaseApiAdapter };\n\n// Usually used as a parent class for custom code\nexport { BaseLZAdapter, BaseUMAdapter };\n\n// Usually used as a standalone class\nexport {\n AssociationLZ,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","import {LRUCache} from './lru_cache';\nimport {clone} from './util';\n\nclass BaseAdapter {\n constructor(config = {}) {\n this._config = config;\n const {\n // Cache control\n cache_enabled = true,\n cache_size = 3,\n } = config;\n this._enable_cache = cache_enabled;\n this._cache = new LRUCache(cache_size);\n }\n\n _buildRequestOptions(options, dependent_data) {\n // Perform any pre-processing required that may influence the request. Receives an array with the payloads\n // for each request that preceded this one in the dependency chain\n // This method may optionally take dependent data into account\n return Object.assign({}, options);\n }\n\n _getCacheKey(options) {\n /* istanbul ignore next */\n if (this._enable_cache) {\n throw new Error('Method not implemented');\n }\n return null;\n }\n\n /**\n * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)\n * @param options\n * @returns {Promise}\n * @private\n */\n _performRequest(options) {\n /* istanbul ignore next */\n throw new Error('Not implemented');\n }\n\n _normalizeResponse(response_text, options) {\n // Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.\n return response_text;\n }\n\n /**\n * Perform custom client-side operations on the retrieved data. For example, add calculated fields or\n * perform rapid client-side filtering on cached data\n * @param records\n * @param {Object} options\n * @returns {*}\n * @private\n */\n _annotateRecords(records, options) {\n return records;\n }\n\n /**\n * A hook to transform the response after all operations are done. For example, this can be used to prefix fields\n * with a namespace unique to the request, like assoc.log_pvalue. (that way, annotations and validation can happen\n * on the actual API payload, without having to guess what the fields were renamed to).\n * @param records\n * @param options\n * @private\n */\n _postProcessResponse(records, options) {\n return records;\n }\n\n getData(options = {}, ...dependent_data) {\n // Public facing method to define, perform, and process the request\n options = this._buildRequestOptions(options, ...dependent_data);\n\n // Then retrieval and parse steps: parse + normalize response, annotate\n const cache_key = this._getCacheKey(options);\n\n let result;\n if (this._enable_cache && this._cache.has(cache_key)) {\n result = this._cache.get(cache_key);\n } else {\n // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)\n // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry\n // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,\n // even if the actual cache key wasn't an exact match\n result = Promise.resolve(this._performRequest(options))\n // Note: we cache the normalized (parsed) response\n .then((text) => this._normalizeResponse(text, options));\n this._cache.add(cache_key, result, options._cache_meta);\n // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request\n // Otherwise, temporary failures couldn't be resolved by trying again in a moment\n result.catch((e) => this._cache.remove(cache_key));\n }\n\n return result\n // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache\n .then((data) => clone(data))\n .then((records) => this._annotateRecords(records, options))\n .then((records) => this._postProcessResponse(records, options));\n }\n}\n\n\n/**\n * Fetch data over the web\n */\nclass BaseUrlAdapter extends BaseAdapter {\n constructor(config = {}) {\n super(config);\n this._url = config.url;\n }\n\n\n // Default cache key is the URL for the request.\n _getCacheKey(options) {\n return this._getURL(options);\n }\n\n _getURL(options) {\n return this._url;\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.\n // (not validating in constructor allows URL adapter to be used as more generic parent class)\n if (!this._url) {\n throw new Error('Web based resources must specify a resource URL as option \"url\"');\n }\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n // In most cases, we store the response as text so that the copy in cache is clean (no mutable references)\n return response.text();\n });\n }\n\n _normalizeResponse(response_text, options) {\n if (typeof response_text === 'string') {\n return JSON.parse(response_text);\n }\n // Some custom usages will return an object directly; return a copy of the object\n return response_text;\n }\n}\n\nexport { BaseAdapter, BaseUrlAdapter };\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import TRANSFORMS from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n // Two scenarios: we are requesting a field by full name, OR there are transforms to apply\n // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`\n const field_pattern = /^(?:\\w+:\\w+|^\\w+)(?:\\|\\w+)*$/;\n if (!field_pattern.test(field)) {\n throw new Error(`Invalid field specifier: '${field}'`);\n }\n\n const [name, ...transforms] = field.split('|');\n\n this.full_name = field; // fieldname + transforms\n this.field_name = name; // just fieldname\n this.transformations = transforms.map((name) => TRANSFORMS.get(name));\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (data[this.field_name] !== undefined) { // Fallback: value sans transforms\n val = data[this.field_name];\n } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations\n val = extra[this.field_name];\n } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {}\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * @module\n * @private\n */\nimport {getLinkedData} from 'undercomplicate';\n\nimport { DATA_OPS } from '../registry';\n\n\nclass DataOperation {\n /**\n * Perform a data operation (such as a join)\n * @param {String} join_type\n * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!\n * @param params Optional user/layout parameters to be passed to the data function\n */\n constructor(join_type, initiator, params) {\n this._callable = DATA_OPS.get(join_type);\n this._initiator = initiator;\n this._params = params || [];\n }\n\n getData(plot_state, ...dependent_recordsets) {\n // Most operations are joins: they receive two pieces of data (eg left + right)\n // Other ops are possible, like consolidating just one set of records to best value per key\n // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]\n\n // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options\n const context = {plot_state, data_layer: this._initiator};\n return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));\n }\n}\n\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a\n * designated order.\n *\n * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can\n * perform its own calculations, filter results, and apply transforms without influencing other layers.\n * (while still respecting a shared cache where appropriate)\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n /**\n * Parse the data layer configuration when a layer is first created.\n * Validate config, and return entities and dependencies in a format usable for data retrieval.\n * This is used by data layers, and also other data-retrieval functions (like subscribeToDate).\n *\n * Inherent assumptions:\n * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed.\n * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be\n * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the\n * removed adapter.\n * @param {Object} namespace_options\n * @param {Array} data_operations\n * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations,\n * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate\n * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care!\n * @returns {Array} Map of entities and list of dependencies\n */\n config_to_sources(namespace_options = {}, data_operations = [], initiator) {\n const entities = new Map();\n const namespace_local_names = Object.keys(namespace_options);\n\n // 1. Specify how to coordinate data. Precedence:\n // a) EXPLICIT fetch logic,\n // b) IMPLICIT auto-generate fetch order if there is only one NS,\n // c) Throw \"spec required\" error if > 1, because 2 adapters may need to be fetched in a sequence\n let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from}\n if (!dependency_order) {\n dependency_order = { type: 'fetch', from: namespace_local_names };\n data_operations.unshift(dependency_order);\n }\n\n // Validate that all NS items are available to the root requester in DataSources. All layers recognize a\n // default value, eg people copying the examples tend to have defined a datasource called \"assoc\"\n const ns_pattern = /^\\w+$/;\n for (let [local_name, global_name] of Object.entries(namespace_options)) {\n if (!ns_pattern.test(local_name)) {\n throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`);\n }\n\n const source = this._sources.get(global_name);\n if (!source) {\n throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`);\n }\n entities.set(local_name, source);\n\n // Note: Dependency spec checker will consider \"ld(assoc)\" to match a namespace called \"ld\"\n if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) {\n // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join.\n // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch\n // Thus the default behavior is \"fetch all namespaces as though they don't depend on anything.\n // If they depend on something, only then does \"data_ops[@type=fetch].from\" need to be mutated\n dependency_order.from.push(local_name);\n }\n }\n\n let dependencies = Array.from(dependency_order.from);\n\n // Now check all joins. Are namespaces valid? Are they requesting known data?\n for (let config of data_operations) {\n let {type, name, requires, params} = config;\n if (type !== 'fetch') {\n let namecount = 0;\n if (!name) {\n name = config.name = `join${namecount}`;\n namecount += 1;\n }\n\n if (entities.has(name)) {\n throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`);\n }\n requires.forEach((require_name) => {\n if (!entities.has(require_name)) {\n throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`);\n }\n });\n\n const task = new DataOperation(type, initiator, params);\n entities.set(name, task);\n dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB)\n }\n }\n return [entities, dependencies];\n }\n\n /**\n * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end)\n * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts.\n * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances\n * (things that implement a method getData).\n * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order\n * @returns {Promise}\n */\n getData(plot_state, entities, dependencies) {\n if (!dependencies.length) {\n return Promise.resolve([]);\n }\n // The last dependency (usually the last join operation) determines the last thing returned.\n return getLinkedData(plot_state, entities, dependencies, true);\n }\n}\n\n\nexport default Requester;\n\nexport {DataOperation as _JoinTask};\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import WIDGETS from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n const options = this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n this.addWidget(layout);\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown)\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Add a new widget to the toolbar.\n * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to:\n * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here.\n * - call widget.show() to ensure that the widget is initialized and rendered correctly\n * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default.\n * @param {Object} layout The layout object describing the desired widget\n * @returns {layout.type}\n */\n addWidget(layout) {\n try {\n const widget = WIDGETS.create(layout.type, layout, this);\n this.widgets.push(widget);\n return widget;\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 12,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n if (Array.isArray(this.parent.data_layers[id].layout.legend)) {\n this.parent.data_layers[id].layout.legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size || 12;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset]\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n if (typeof layout.id !== 'string' || !layout.id) {\n throw new Error('Panel layouts must specify \"id\"');\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this._layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this._state_id = this.id;\n this.state[this._state_id] = this.state[this._state_id] || {};\n } else {\n this.state = null;\n this._state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this._data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this._data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this._zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this._event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this._event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this._data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this._data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id]._layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n const target_layer = this.data_layers[id];\n if (!target_layer) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n target_layer.destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (target_layer.svg.container) {\n target_layer.svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(target_layer._layout_idx, 1);\n delete this.state[target_layer._state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id]._layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n const { cliparea } = this.layout;\n\n // Set and position the inner border, style if necessary\n const { margin } = this.layout;\n this.inner_border\n .attr('x', margin.left)\n .attr('y', margin.top)\n .attr('width', this.parent_plot.layout.width - (margin.left + margin.right))\n .attr('height', this.layout.height - (margin.top + margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n const axes_config = this.layout.axes;\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (axes_config.x.range) {\n base_x_range.start = axes_config.x.range.start || base_x_range.start;\n base_x_range.end = axes_config.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: cliparea.height, end: 0 };\n if (axes_config.y1.range) {\n base_y1_range.start = axes_config.y1.range.start || base_y1_range.start;\n base_y1_range.end = axes_config.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: cliparea.height, end: 0 };\n if (axes_config.y2.range) {\n base_y2_range.start = axes_config.y2.range.start || base_y2_range.start;\n base_y2_range.end = axes_config.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n let { _interaction } = this.parent;\n const current_drag = _interaction.dragging;\n if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (_interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = _interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = _interaction.zooming.center - margin.left - this.layout.origin.x;\n const offset_ratio = anchor / cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (current_drag) {\n switch (current_drag.method) {\n case 'background':\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n } else {\n anchor = current_drag.start_x - margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${current_drag.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y;\n ranges[y_shifted][1] = +current_drag.dragged_y;\n } else {\n anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3);\n ranges[y_shifted][0] = cliparea.height;\n ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent._interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n // Redefine b/c might have been changed during call to parent re-render\n _interaction = this.parent._interaction;\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this._zoom_timeout !== null) {\n clearTimeout(this._zoom_timeout);\n }\n this._zoom_timeout = setTimeout(() => {\n this.parent._interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this._initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((id) => {\n const axis = this.layout.axes[id];\n if (!Object.keys(axis).length || axis.render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n axis.render = false;\n } else {\n axis.render = true;\n axis.label = axis.label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n const layout = this.layout;\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n layout.height = Math.max(Math.round(+height), layout.min_height);\n }\n }\n layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0);\n layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', layout.height);\n }\n if (this._initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n const { cliparea, margin } = this.layout;\n if (!isNaN(top) && top >= 0) {\n margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (margin.top + margin.bottom > this.layout.height) {\n extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2);\n margin.top -= extra;\n margin.bottom -= extra;\n }\n if (margin.left + margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2);\n margin.left -= extra;\n margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n margin[m] = Math.max(margin[m], 0);\n });\n cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0);\n cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0);\n cliparea.origin.x = margin.left;\n cliparea.origin.y = margin.top;\n\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this._data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent._panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n const { parent } = this;\n const y_index = this.layout.y_index;\n if (parent._panel_ids_by_y_index[y_index - 1]) {\n parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1];\n parent._panel_ids_by_y_index[y_index - 1] = this.id;\n parent.applyPanelYIndexesToPanelLayouts();\n parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n const { _panel_ids_by_y_index } = this.parent;\n if (_panel_ids_by_y_index[this.layout.y_index + 1]) {\n _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1];\n _panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this._data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this._data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this._data_promises)\n .then(() => {\n this._initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([\\w+_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future.\n * @event data_from_layer\n * @property {object} data\n * @property {String} data.layer The fully qualified ID of the layer emitting this event\n * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum\n * element. It reflects all namespaces and data operations used by that layer.\n * @see event:any_lz_event\n */\n\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this._initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this._panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this._remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this._interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this._event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this._event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this._panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this._panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id]._layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer._layer_state;\n delete this.layout.state[layer._state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n const target_panel = this.panels[id];\n if (!target_panel) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this._panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n target_panel.loader.hide();\n target_panel.toolbar.destroy(true);\n target_panel.curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (target_panel.svg.container) {\n target_panel.svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(target_panel._layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id]._layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the\n * structure of the data, instead of just displaying something.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @listens event:data_from_layer\n * @param {Object} [opts] Options\n * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched.\n * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details.\n * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested,\n * this is usually required in order to specify dependency order and join operations. See data layer documentation for details.\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(opts, success_callback) {\n const { from_layer, namespace, data_operations, onerror } = opts;\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = onerror || function (err) {\n console.error('An error occurred while acting on an external callback', err);\n };\n\n if (from_layer) {\n // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places.\n const base_prefix = `${this.getBaseId()}.`;\n // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code.\n const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`;\n\n // Ensure that a valid layer exists to watch\n let is_valid_layer = false;\n for (let p of Object.values(this.panels)) {\n is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target);\n if (is_valid_layer) {\n break;\n }\n }\n if (!is_valid_layer) {\n throw new Error(`Could not subscribe to unknown data layer ${layer_target}`);\n }\n\n const listener = (eventData) => {\n if (eventData.data.layer !== layer_target) {\n // Same event name fires for many layers; only fire success cb for the one layer we want\n return;\n }\n try {\n success_callback(eventData.data.content, this);\n } catch (error) {\n error_callback(error);\n }\n };\n\n this.on('data_from_layer', listener);\n return listener;\n }\n\n // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of\n // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized\n // in connection to some other visualization)\n if (!namespace) {\n throw new Error(\"subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option\");\n }\n\n const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot.\n const listener = () => {\n try {\n // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely,\n // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame.\n this.lzd.getData(this.state, entities, dependencies)\n .then((new_data) => success_callback(new_data, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this._remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this._remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this._remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this._initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /**\n * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another,\n * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties,\n * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.\n * @public\n */\n mutateLayout() {\n Object.values(this.panels).forEach((panel) => {\n Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout());\n });\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n const { _interaction } = this;\n if (panel_id) {\n return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(_interaction.dragging || _interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this._panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this._initialized) {\n this._panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let panel of Object.values(this.panels)) {\n if (panel.layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_layout = panel.layout;\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel_layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel_layout.margin.right, 0);\n panel_layout.width += delta;\n panel_layout.margin.left = x_linked_margins.left;\n panel_layout.margin.right = x_linked_margins.right;\n panel_layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n const resize_listener = () => this.rescaleSVG();\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener\n // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed\n if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers\n const options = { root: document.documentElement, threshold: 0.9 };\n const observer = new IntersectionObserver((entries, observer) => {\n if (entries.some((entry) => entry.intersectionRatio > 0)) {\n this.rescaleSVG();\n }\n }, options);\n // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup\n observer.observe(this.container);\n }\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this._mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this._panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent._panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent._panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this._panel_boundaries.hide_timeout);\n this._panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this._panel_boundaries.hide_timeout = setTimeout(() => {\n this._panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this._mouse_guide.vertical.attr('x', -1);\n this._mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this._mouse_guide.vertical.attr('x', coords[0]);\n this._mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n const { _interaction } = this;\n if (_interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x;\n _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y;\n this.panels[_interaction.panel_id].render();\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this._initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this._interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n const { _interaction } = this;\n if (!_interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[_interaction.panel_id] != 'object') {\n this._interaction = {};\n return this;\n }\n const panel = this.panels[_interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel._data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (_interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (_interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (_interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(_interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this._interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} value value\n */\nconst if_value = (parameters, value) => {\n if (typeof value == 'undefined' || parameters.field_value !== value) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} value value\n * @returns {*}\n */\nconst numerical_bin = (parameters, value) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+value < prev || (+value >= prev && +value < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @function ordinal_cycle\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n *\n * @function stable_choice\n *\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, value) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return nullval;\n }\n if (+value <= parameters.breaks[0]) {\n return values[0];\n } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +value && breaks[idx] >= +value) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\n/**\n * Calculate the effect direction based on beta, or the combination of beta and standard error.\n * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.\n *\n * @function effect_direction\n * @param parameters\n * @param parameters.'+' The value to return if the effect direction is positive\n * @param parameters.'-' The value to return if the effect direction is positive\n * @param parameters.beta_field The name of the field containing beta\n * @param parameters.stderr_beta_field The name of the field containing stderr_beta\n * @param {Object} input This function should receive the entire datum object, rather than one single field\n * @returns {null}\n */\nfunction effect_direction(parameters, input) {\n if (input === undefined) {\n return null;\n }\n\n const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters;\n\n if (!beta_field || !stderr_beta_field) {\n throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`);\n }\n\n const beta_val = input[beta_field];\n const se_val = input[stderr_beta_field];\n\n if (beta_val !== undefined) {\n if (se_val !== undefined) {\n if ((beta_val - 2 * se_val) > 0) {\n return plus_result;\n } else if ((beta_val + 2 * se_val) < 0) {\n return neg_result || null;\n }\n } else {\n if (beta_val > 0) {\n return plus_result;\n } else if (beta_val < 0) {\n return neg_result;\n }\n }\n }\n // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid\n // about expected data formats for layouts.\n return null;\n}\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, findFields, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n/**\n * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.\n * @property {module:LocusZoom_DataFunctions|'fetch'} type\n * @property {String[]} [from] For operations of type \"fetch\", this is required. By default, it will fill in any items provided in \"namespace\" (everything specified in namespace triggers an adapter/network request)\n * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace).\n * Eg, for ld to be fetched after association data, specify \"ld(assoc)\". Most LocusZoom examples fill in all items, in order to make the examples more clear.\n * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation.\n * Eg, if the retrieved data is combined via several left joins in series: `name: \"assoc_plus_ld\"` -> feeds into `{name: \"final\", requires: [\"assoc_plus_ld\"]}`\n * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just\n * the names of other namespaces (or data operations) whose results must be available before a join can be performed\n * @property {String[]} params Any user-defined parameters that should be passed to the particular join function\n * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']`\n * Separate from this section, data functions will also receive a copy of \"plot.state\" automatically.\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or line for a path.\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\".\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n namespace: {},\n data_operations: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is\n * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely\n * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is\n * your job to assure that all of the expected fields are present in every element)\n * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data (\"assoc\")\n * to the globally unique name for something defined in LocusZoom.DataSources.\n * Namespaces allow a single layout to be reused to plot many tracks of the same type: \"given some form of association data, plot it\".\n * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse\n * a layout with a new provider of data- like plotting two association studies stacked together-\n * only the namespace section of the layout needs to be overridden.\n * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})`\n * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions})\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this._layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this._state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this._layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this._tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this._global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n\n // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval\n this._data_contract = new Set(); // List of all fields requested by the layout\n this._entities = new Map();\n this._dependencies = [];\n this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout\n }\n\n /****** Public interface: methods for manipulating the layer from other parts of LZ */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index + 1]) {\n layer_order[current_index] = layer_order[current_index + 1];\n layer_order[current_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index - 1]) {\n layer_order[current_index] = layer_order[current_index - 1];\n layer_order[current_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this._layer_state.extra_fields[id]) {\n this._layer_state.extra_fields[id] = {};\n }\n this._layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /**\n * A list of operations that should be run when the layout is mutated\n * Typically, these are things done once when a layout is first specified, that would not automatically\n * update when the layout was changed.\n * @public\n */\n mutateLayout() {\n // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract.\n if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester\n const { namespace, data_operations } = this.layout;\n this._data_contract = findFields(this.layout, Object.keys(namespace));\n const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this);\n this._entities = entities;\n this._dependencies = dependencies;\n }\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n *\n * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that\n * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data),\n * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}`\n * @protected\n * @param {Object} element The data associated with a particular element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression\n const id_field = this.layout.id_field;\n let value = element[id_field];\n if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) {\n // No field value was found directly, but if it looks like a template expression, next, try parsing that\n // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot!\n value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient\n }\n if (value === null || value === undefined) {\n // Neither exact field nor template options produced an ID\n throw new Error('Unable to generate element ID');\n }\n const element_id = value.toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Abstract method. It should be overridden by data layers that implement separate status\n * nodes, such as genes or intervals.\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or\n * a group of unrelated intervals (all markings grouped within a category).\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n\n // Does the data from the API satisfy the list of fields expected by this layout?\n // Not every record will have every possible field (example: left joins like assoc + ld). The check is \"did\n // we see this field at least once in any record at all\".\n if (this.data.length && this._data_contract.size) {\n const fields_unseen = new Set(this._data_contract);\n for (let record of this.data) {\n Object.keys(record).forEach((field) => fields_unseen.delete(field));\n if (!fields_unseen.size) {\n // Once every requested field has been seen in at least one record, no need to look at more records\n break;\n }\n }\n if (fields_unseen.size) {\n // Current implementation is a soft warning, so that certain \"incremental enhancement\" features\n // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info.\n // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data.\n console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in \"data_operations\" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`);\n }\n }\n\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value);\n }\n\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this._layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const _layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = _layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this._state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this._state_id] = _layer_state;\n }\n this._layer_state = _layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this._tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this._tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this._layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this._tooltips[id].selector.html('');\n this._tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this._tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this._tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this._tooltips[id]) {\n if (typeof this._tooltips[id].selector == 'object') {\n this._tooltips[id].selector.remove();\n }\n delete this._tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this._layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this._tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this._tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this._tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this._tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n const tooltip_layout = this.layout.tooltip;\n if (typeof tooltip_layout != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof tooltip_layout.show == 'string') {\n show_directive = { and: [ tooltip_layout.show ] };\n } else if (typeof tooltip_layout.show == 'object') {\n show_directive = tooltip_layout.show;\n }\n\n let hide_directive = {};\n if (typeof tooltip_layout.hide == 'string') {\n hide_directive = { and: [ tooltip_layout.hide ] };\n } else if (typeof tooltip_layout.hide == 'object') {\n hide_directive = tooltip_layout.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const _layer_state = this._layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (_layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this._layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this._layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this._layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this._layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self._state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies)\n .then((new_data) => {\n this.data = new_data;\n this.applyDataMethods();\n this._initialized = true;\n // Allow listeners (like subscribeToData) to see the information associated with a layer\n this.parent.emit(\n 'data_from_layer',\n { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin\n true\n );\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 12,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n tooltip: null,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this._global_statuses).forEach((global_status) => {\n if (this._global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n data_layer._label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this._label_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer._label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer._label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer._label_texts.nodes();\n data_layer._label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this._label_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this._label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this._label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this._label_texts) {\n this._label_texts.remove();\n }\n\n this._label_texts = this._label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this._label_lines) {\n this._label_lines.remove();\n }\n this._label_lines = this._label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this._label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this._label_texts) {\n this._label_texts.remove();\n }\n if (this._label_lines) {\n this._label_lines.remove();\n }\n if (this._label_groups) {\n this._label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this._label_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{assoc:variant|htmlescape}}
\n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
\n Ref. Allele: {{assoc:ref_allele|htmlescape}}
\n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
`,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

{{gene_name|htmlescape}}

'\n + 'Gene ID: {{gene_id|htmlescape}}
'\n + 'Transcript ID: {{transcript_id|htmlescape}}
'\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
ConstraintExpected variantsObserved variantsConst. Metric
Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

{{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{catalog:variant|htmlescape}}
'\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
'\n + 'Top Trait: {{catalog:trait|htmlescape}}
'\n + 'Top P Value: {{catalog:log_pvalue|logtoscinotation}}
'\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
' +\n '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
' +\n 'Promoter
' +\n '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
' +\n '{{#if access:target}}Target: {{access:target|htmlescape}}
{{/if}}' +\n 'Score: {{access:score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n id: 'recombrate',\n namespace: { 'recomb': 'recomb' },\n data_operations: [\n { type: 'fetch', from: ['recomb'] },\n ],\n type: 'line',\n tag: 'recombination',\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: 'recomb:position',\n },\n y_axis: {\n axis: 2,\n field: 'recomb:recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)'],\n },\n {\n type: 'left_match',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'ld'],\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n id_field: 'assoc:variant',\n coalesce: {\n active: true,\n },\n point_shape: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 'diamond',\n },\n },\n {\n // Not every dataset will provide these params\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'assoc:beta',\n stderr_beta_field: 'assoc:se',\n },\n },\n 'circle',\n ],\n point_size: {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: 'ld:correlation',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { shape: 'diamond', color: '#9632b8', size: 40, label: 'LD Ref Var', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(219, 61, 17)', size: 40, label: '1.0 > r² ≥ 0.8', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(248, 195, 42)', size: 40, label: '0.8 > r² ≥ 0.6', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(110, 254, 104)', size: 40, label: '0.6 > r² ≥ 0.4', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(38, 188, 225)', size: 40, label: '0.4 > r² ≥ 0.2', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(70, 54, 153)', size: 40, label: '0.2 > r² ≥ 0.0', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#AAAAAA', size: 40, label: 'no r² data', class: 'lz-data_layer-scatter' },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: 'assoc:position',\n },\n y_axis: {\n axis: 1,\n field: 'assoc:log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n namespace: { 'access': 'access' },\n data_operations: [\n { type: 'fetch', from: ['access'] },\n ],\n match: { send: 'access:target', receive: 'access:target' },\n // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.\n id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',\n filters: [\n { field: 'access:score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: 'access:start1',\n field2: 'access:start2',\n },\n y_axis: {\n axis: 1,\n field: 'access:score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n\n base.data_operations.push({\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_catalog',\n requires: ['assoc_plus_ld', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n });\n\n base.tooltip.html += '{{#if catalog:rsid}}
See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n return base;\n}();\n\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n namespace: { 'phewas': 'phewas' },\n data_operations: [\n { type: 'fetch', from: ['phewas'] },\n ],\n point_shape: [\n {\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'phewas:beta',\n stderr_beta_field: 'phewas:se',\n },\n },\n 'circle',\n ],\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',\n x_axis: {\n field: 'lz_auto_x', // Automatically added by the category_scatter layer\n category_field: 'phewas:trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: 'phewas:log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: 'phewas:trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Trait: {{phewas:trait_label|htmlescape}}
\nTrait Category: {{phewas:trait_group|htmlescape}}
\nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
β: {{phewas:beta|scinotation|htmlescape}}
{{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}`,\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{phewas:trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: 'phewas:log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n data_operations: [\n {\n type: 'fetch',\n from: ['gene', 'constraint(gene)'],\n },\n {\n name: 'gene_constraint',\n type: 'genes_to_gnomad_constraint',\n requires: ['gene', 'constraint'],\n },\n ],\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n data_operations: [\n {\n type: 'fetch', from: ['assoc', 'catalog'],\n },\n {\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n },\n ],\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: '#0000CC',\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'catalog:rsid', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n }\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 225,\n margin: { top: 35, right: 50, bottom: 40, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 28,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 40,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 55, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 50, bottom: 40, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 28,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{catalog:trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: 'catalog:trait', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: 'ld:correlation', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '10px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 50, bottom: 20, left: 50 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu)\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 50, bottom: 120, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 28,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 50, bottom: 10, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel)\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or applying namespaces.\n let base = super.get(type).get(name);\n\n // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.\n // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)\n const custom_namespaces = overrides.namespace;\n if (!base.namespace) {\n // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout\n // NOTE: The \"merge namespace\" behavior means that data layers can add new data easily, but this method\n // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).\n delete overrides.namespace;\n }\n let result = merge(overrides, base);\n\n if (custom_namespaces) {\n result = applyNamespaces(result, custom_namespaces);\n }\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n\n // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested\n // from external sources. This is purely a hint, because not every layout is generated through the registry.\n if (type === 'data_layer' && copy.namespace) {\n copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();\n }\n\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","/**\n * \"Data operation\" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results\n *\n * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation\n * is a \"join\", such as combining association + LD together into a single set of records for plotting. Several join\n * functions (that operate by analogy to SQL) are provided built-in.\n *\n * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):\n * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.\n * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,\n * this is the recommended path to do so)\n * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),\n * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,\n * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks\n * (PheWAS), automatic panel legends or color schemes (BED tracks), etc.\n *\n * Usually, a data operation receives two recordsets (the left and right members of the join, like \"assoc\" and \"ld\").\n * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network\n * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is\n * uncommon. (if possible, try to provide your data with fewer adapters/network requests!)\n *\n * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,\n * particularly for advanced features, may carry assumptions about field names/ formatting.\n * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,\n * or it may match two datasets based on a specific way of identifying the variant)\n *\n * @module LocusZoom_DataFunctions\n */\nimport {joins} from 'undercomplicate';\n\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"data join\" functions.\n * @alias module:LocusZoom~DataFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\nfunction _wrap_join(handle) {\n // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).\n\n // Many of our join functions are implemented with a different number of arguments than what a datafunction\n // actually receives. (eg, a join function is generic and doesn't care about \"context\" information like plot.state)\n // This wrapper is simple shared code to handle required validation and conversion stuff.\n return (context, deps, ...params) => {\n if (deps.length !== 2) {\n throw new Error('Join functions must receive exactly two recordsets');\n }\n return handle(...deps, ...params);\n };\n}\n\n// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to\n// pick the most significant claim in the catalog for a variant, rather than joining every possible match.\n// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.\nfunction assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {\n if (!assoc_data.length) {\n return assoc_data;\n }\n\n // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each\n const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);\n\n const catalog_flat = []; // Store only the top significant claim for each catalog variant entry\n for (let claims of catalog_by_variant.values()) {\n // Find max item within this set of claims, push that to catalog_\n let best = 0;\n let best_variant;\n for (let item of claims) {\n const val = item[catalog_logp_name];\n if ( val >= best) {\n best_variant = item;\n best = val;\n }\n }\n best_variant.n_catalog_matches = claims.length;\n catalog_flat.push(best_variant);\n }\n return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);\n}\n\n// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.\nfunction genes_to_gnomad_constraint(genes_data, constraint_data) {\n genes_data.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return genes_data;\n}\n\n\n/**\n * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset\n *\n * @function\n * @name left_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('left_match', _wrap_join(joins.left_match));\n\n/**\n * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.\n *\n * @function\n * @name inner_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('inner_match', _wrap_join(joins.inner_match));\n\n/**\n * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.\n *\n * @function\n * @name full_outer_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('full_outer_match', _wrap_join(joins.full_outer_match));\n\n/**\n * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.\n *\n * @function\n * @name assoc_to_gwas_catalog\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: assoc records, then catalog records\n * @param {String} assoc_key The name of the key field in association data, eg variant ID\n * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID\n * @param {String} catalog_log_p_name The name of the \"log_pvalue\" field in gwas catalog data, used to choose the most significant claim for a given variant\n */\nregistry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));\n\n/**\n * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).\n *\n * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.\n *\n * @function\n * @name genes_to_gnomad_constraint\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data\n */\nregistry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));\n\nexport default registry;\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n DATA_OPS as DataFunctions,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n WIDGETS as Widgets,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n DataFunctions,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/docs/api/LayoutRegistry.html b/docs/api/LayoutRegistry.html index 659fb5bc..961ef27e 100644 --- a/docs/api/LayoutRegistry.html +++ b/docs/api/LayoutRegistry.html @@ -288,7 +288,7 @@
Parameters:
Source:
@@ -498,7 +498,7 @@
Parameters:
Source:
@@ -677,7 +677,7 @@
Parameters:
Source:
@@ -963,7 +963,7 @@
Parameters:
Source:
@@ -1189,7 +1189,7 @@
Parameters:
Source:
@@ -1357,7 +1357,7 @@
Parameters:
Source:
@@ -1424,7 +1424,7 @@
Returns:

diff --git a/docs/api/Line.html b/docs/api/Line.html index e2e22c07..3ae26855 100644 --- a/docs/api/Line.html +++ b/docs/api/Line.html @@ -379,7 +379,7 @@
Properties
Source:
@@ -487,7 +487,7 @@

renderSource:
@@ -673,7 +673,7 @@
Parameters:
Source:
@@ -719,7 +719,7 @@
Parameters:


diff --git a/docs/api/Panel.html b/docs/api/Panel.html index 2178c81f..8aa900fa 100644 --- a/docs/api/Panel.html +++ b/docs/api/Panel.html @@ -105,8 +105,6 @@
Parameters:
- <optional>
- @@ -117,12 +115,10 @@
Parameters:
- '' - -

An identifier string that must be unique across all panels in the plot

+

An identifier string that must be unique across all panels in the plot. Required.

@@ -2032,11 +2028,15 @@
Type:
-

(protected) curtain :Object

+

(protected) _event_hooks :Object

+
+

Known event hooks that the panel can respond to

+
+
Type:
@@ -2082,13 +2082,20 @@
Type:
Source:
+
See:
+
+ +
+ @@ -2100,22 +2107,18 @@
Type:
-

data_layers :Object.<String, BaseDataLayer>

+

(protected) curtain :Object

-
-

Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.

-
-
Type:

diff --git a/docs/api/Plot.html b/docs/api/Plot.html index 00062a08..e2f21576 100644 --- a/docs/api/Plot.html +++ b/docs/api/Plot.html @@ -580,7 +580,7 @@
Parameters:
Source:
@@ -702,7 +702,7 @@
Type:
-

(protected) event_hooks :Object

+

(protected) _event_hooks :Object

@@ -756,7 +756,7 @@
Type:
Source:
@@ -831,7 +831,7 @@
Type:
Source:
@@ -904,7 +904,7 @@
Type:
Source:
@@ -976,7 +976,7 @@
Type:
Source:
@@ -1050,7 +1050,7 @@
Type:
Source:
@@ -1179,7 +1179,7 @@
Parameters:
Source:
@@ -1334,7 +1334,7 @@
Parameters:
Source:
@@ -1464,7 +1464,7 @@

destroySource:
@@ -1624,7 +1624,7 @@
Parameters:
Source:
@@ -1679,6 +1679,96 @@
Returns:
+ + + + + + +

mutateLayout()

+ + + + + + +
+

Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another, +these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties, +like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.

+
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -1829,7 +1919,7 @@
Parameters:
Source:
@@ -2018,7 +2108,7 @@
Parameters:
Source:
@@ -2135,7 +2225,7 @@

refreshSource:
@@ -2290,7 +2380,7 @@
Parameters:
Source:
@@ -2349,7 +2439,7 @@
Returns:
-

subscribeToData(fields, success_callback, optsopt) → {function}

+

subscribeToData(optsopt, success_callback) → {function}

@@ -2398,13 +2488,13 @@
Parameters:
- fields + opts -Array.<String> +Object @@ -2413,6 +2503,8 @@
Parameters:
+ <optional>
+ @@ -2422,21 +2514,43 @@
Parameters:
-

An array of field names and transforms, in the same syntax used by a data layer. -Different data sources should be prefixed by the namespace name.

- +

Options

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + - + + - + - + + -
NameTypeAttributesDefaultDescription
success_callbackfrom_layer -externalDataCallback +String @@ -2445,6 +2559,8 @@
Parameters:
+ <optional>
+ @@ -2453,16 +2569,21 @@
Parameters:
+
+ + null + +

Used defined function that is automatically called any time that -new data is received by the plot. Receives two arguments: (data, plot).

The ID string (panel_id.layer_id) of a specific data layer to be watched.

optsnamespace @@ -2487,44 +2608,25 @@
Parameters:
- -

Options

-
Properties
+
+
- - - - - - - - - - - - - - - - - - - + + - - + - + - + - + @@ -2601,6 +2700,38 @@
Properties
+ + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription

An object specifying where to find external data. See data layer documentation for details.

onerrordata_operations -externalErrorCallback +Object @@ -2548,21 +2650,21 @@
Properties

User defined function that is automatically called if a problem -occurs during the data request or subsequent callback operations

An array of data operations. If more than one source of data is requested, +this is usually required in order to specify dependency order and join operations. See data layer documentation for details.

discreteonerror -boolean +externalErrorCallback @@ -2583,14 +2685,11 @@
Properties
- false -

Normally the callback will subscribe to the combined body from the chain, -which may not be in a format that matches what the external callback wants to do. If discrete=true, returns the -uncombined record info

User defined function that is automatically called if a problem +occurs during the data request or subsequent callback operations

success_callback + + +externalDataCallback + + + + + + + + + +

Used defined function that is automatically called any time that +new data is received by the plot. Receives two arguments: (data, plot).

@@ -2638,7 +2769,7 @@
Properties
Source:
@@ -2658,6 +2789,8 @@
Properties
Listens to Events:
@@ -2850,7 +2983,7 @@
Parameters:
Source:
@@ -2896,7 +3029,7 @@
Parameters:

diff --git a/docs/api/TransformationFunctionsRegistry.html b/docs/api/TransformationFunctionsRegistry.html index b344f6f7..dba0482a 100644 --- a/docs/api/TransformationFunctionsRegistry.html +++ b/docs/api/TransformationFunctionsRegistry.html @@ -305,7 +305,7 @@
Parameters:

diff --git a/docs/api/components_data_layer_annotation_track.js.html b/docs/api/components_data_layer_annotation_track.js.html index 40991901..df096c6e 100644 --- a/docs/api/components_data_layer_annotation_track.js.html +++ b/docs/api/components_data_layer_annotation_track.js.html @@ -171,7 +171,7 @@

Source: components/data_layer/annotation_track.js


diff --git a/docs/api/components_data_layer_arcs.js.html b/docs/api/components_data_layer_arcs.js.html index b0036a83..2620f2e8 100644 --- a/docs/api/components_data_layer_arcs.js.html +++ b/docs/api/components_data_layer_arcs.js.html @@ -180,7 +180,7 @@

Source: components/data_layer/arcs.js


diff --git a/docs/api/components_data_layer_base.js.html b/docs/api/components_data_layer_base.js.html index 40b2ba9a..2df4c105 100644 --- a/docs/api/components_data_layer_base.js.html +++ b/docs/api/components_data_layer_base.js.html @@ -41,7 +41,7 @@

Source: components/data_layer/base.js

import {STATUSES} from '../constants'; import Field from '../../data/field'; import {parseFields} from '../../helpers/display'; -import {deepCopy, merge} from '../../helpers/layouts'; +import {deepCopy, findFields, merge} from '../../helpers/layouts'; import MATCHERS from '../../registry/matchers'; import SCALABLE from '../../registry/scalable'; @@ -80,6 +80,21 @@

Source: components/data_layer/base.js

* @property value The target value to compare to */ +/** + * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting. + * @property {module:LocusZoom_DataFunctions|'fetch'} type + * @property {String[]} [from] For operations of type "fetch", this is required. By default, it will fill in any items provided in "namespace" (everything specified in namespace triggers an adapter/network request) + * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace). + * Eg, for ld to be fetched after association data, specify "ld(assoc)". Most LocusZoom examples fill in all items, in order to make the examples more clear. + * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation. + * Eg, if the retrieved data is combined via several left joins in series: `name: "assoc_plus_ld"` -> feeds into `{name: "final", requires: ["assoc_plus_ld"]}` + * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just + * the names of other namespaces (or data operations) whose results must be available before a join can be performed + * @property {String[]} params Any user-defined parameters that should be passed to the particular join function + * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']` + * Separate from this section, data functions will also receive a copy of "plot.state" automatically. + */ + /** * @typedef {object} LegendItem @@ -106,13 +121,13 @@

Source: components/data_layer/base.js

* A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user. * @memberof module:LocusZoom_DataLayers~BaseDataLayer * @protected - * @type {{type: string, fields: Array, x_axis: {}, y_axis: {}}} */ const default_layout = { id: '', type: '', tag: 'custom_data_type', - fields: [], + namespace: {}, + data_operations: [], id_field: 'id', filters: null, match: {}, @@ -138,12 +153,19 @@

Source: components/data_layer/base.js

* layer that shows association scatter plots, anywhere": even if the IDs are different, the tag can be the same. * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown. * (see: {@link LayoutRegistry.mutate_attrs}) - * @param {String[]} layout.fields A list of (namespaced) fields specifying what data is used by the layer. Only - * these fields will be made available to the data layer, and only data sources (namespaces) referred to in - * this array will be fetched. This represents the "contract" between what data is returned and what data is rendered. - * This fields array works in concert with the data retrieval method BaseAdapter.extractFields. * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse - * events, etc. This should be unique to the specified datum. + * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is + * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely + * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is + * your job to assure that all of the expected fields are present in every element) + * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data ("assoc") + * to the globally unique name for something defined in LocusZoom.DataSources. + * Namespaces allow a single layout to be reused to plot many tracks of the same type: "given some form of association data, plot it". + * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse + * a layout with a new provider of data- like plotting two association studies stacked together- + * only the namespace section of the layout needs to be overridden. + * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})` + * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions}) * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact * details vary from one layer to the next. See the Interactivity Tutorial for details. @@ -195,12 +217,12 @@

Source: components/data_layer/base.js

* @private * @member {Boolean} */ - this.initialized = false; + this._initialized = false; /** * @private * @member {Number} */ - this.layout_idx = null; + this._layout_idx = null; /** * The unique identifier for this layer. Should be unique within this panel. @@ -285,13 +307,13 @@

Source: components/data_layer/base.js

* @private * @member {String} */ - this.state_id = null; + this._state_id = null; /** * @private * @member {Object} * */ - this.layer_state = null; + this._layer_state = null; // Create a default state (and set any references to the parent as appropriate) this._setDefaultState(); @@ -309,19 +331,25 @@

Source: components/data_layer/base.js

* @private * @member {Object} */ - this.tooltips = {}; + this._tooltips = {}; } // Initialize flags for tracking global statuses - this.global_statuses = { + this._global_statuses = { 'highlighted': false, 'selected': false, 'faded': false, 'hidden': false, }; + + // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval + this._data_contract = new Set(); // List of all fields requested by the layout + this._entities = new Map(); + this._dependencies = []; + this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout } - /****** Public interface: methods for external manipulation */ + /****** Public interface: methods for manipulating the layer from other parts of LZ */ /** * @public @@ -336,9 +364,11 @@

Source: components/data_layer/base.js

* @returns {BaseDataLayer} */ moveForward() { - if (this.parent.data_layer_ids_by_z_index[this.layout.z_index + 1]) { - this.parent.data_layer_ids_by_z_index[this.layout.z_index] = this.parent.data_layer_ids_by_z_index[this.layout.z_index + 1]; - this.parent.data_layer_ids_by_z_index[this.layout.z_index + 1] = this.id; + const layer_order = this.parent._data_layer_ids_by_z_index; + const current_index = this.layout.z_index; + if (layer_order[current_index + 1]) { + layer_order[current_index] = layer_order[current_index + 1]; + layer_order[current_index + 1] = this.id; this.parent.resortDataLayers(); } return this; @@ -350,9 +380,11 @@

Source: components/data_layer/base.js

* @returns {BaseDataLayer} */ moveBack() { - if (this.parent.data_layer_ids_by_z_index[this.layout.z_index - 1]) { - this.parent.data_layer_ids_by_z_index[this.layout.z_index] = this.parent.data_layer_ids_by_z_index[this.layout.z_index - 1]; - this.parent.data_layer_ids_by_z_index[this.layout.z_index - 1] = this.id; + const layer_order = this.parent._data_layer_ids_by_z_index; + const current_index = this.layout.z_index; + if (layer_order[current_index - 1]) { + layer_order[current_index] = layer_order[current_index - 1]; + layer_order[current_index - 1] = this.id; this.parent.resortDataLayers(); } return this; @@ -373,10 +405,10 @@

Source: components/data_layer/base.js

*/ setElementAnnotation (element, key, value) { const id = this.getElementId(element); - if (!this.layer_state.extra_fields[id]) { - this.layer_state.extra_fields[id] = {}; + if (!this._layer_state.extra_fields[id]) { + this._layer_state.extra_fields[id] = {}; } - this.layer_state.extra_fields[id][key] = value; + this._layer_state.extra_fields[id][key] = value; return this; } @@ -391,6 +423,23 @@

Source: components/data_layer/base.js

this._filter_func = func; } + /** + * A list of operations that should be run when the layout is mutated + * Typically, these are things done once when a layout is first specified, that would not automatically + * update when the layout was changed. + * @public + */ + mutateLayout() { + // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract. + if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester + const { namespace, data_operations } = this.layout; + this._data_contract = findFields(this.layout, Object.keys(namespace)); + const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this); + this._entities = entities; + this._dependencies = dependencies; + } + } + /********** Protected methods: useful in subclasses to manipulate data layer behaviors */ /** * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other @@ -414,8 +463,12 @@

Source: components/data_layer/base.js

/** * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors. + * + * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that + * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data), + * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}` * @protected - * @param {Object} element + * @param {Object} element The data associated with a particular element * @returns {String} */ getElementId (element) { @@ -425,11 +478,19 @@

Source: components/data_layer/base.js

return element[id_key]; } - const id_field = this.layout.id_field || 'id'; - if (typeof element[id_field] == 'undefined') { + // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression + const id_field = this.layout.id_field; + let value = element[id_field]; + if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) { + // No field value was found directly, but if it looks like a template expression, next, try parsing that + // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot! + value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient + } + if (value === null || value === undefined) { + // Neither exact field nor template options produced an ID throw new Error('Unable to generate element ID'); } - const element_id = element[id_field].toString().replace(/\W/g, ''); + const element_id = value.toString().replace(/\W/g, ''); // Cache ID value for future calls const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\],])/g, '_'); @@ -438,11 +499,11 @@

Source: components/data_layer/base.js

} /** + * Abstract method. It should be overridden by data layers that implement separate status + * nodes, such as genes or intervals. * Fetch an ID that may bind a data element to a separate visual node for displaying status - * Examples of this might be separate visual nodes to show select/highlight statuses, or - * even a common/shared node to show status across many elements in a set. - * Abstract method. It should be overridden by data layers that implement seperate status - * nodes specifically to the use case of the data layer type. + * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or + * a group of unrelated intervals (all markings grouped within a category). * @private * @param {String|Object} element * @returns {String|null} @@ -485,23 +546,39 @@

Source: components/data_layer/base.js

const broadcast_value = this.parent_plot.state.lz_match_value; // Match functions are allowed to use transform syntax on field values, but not (yet) UI "annotations" const field_resolver = field_to_match ? new Field(field_to_match) : null; + + // Does the data from the API satisfy the list of fields expected by this layout? + // Not every record will have every possible field (example: left joins like assoc + ld). The check is "did + // we see this field at least once in any record at all". + if (this.data.length && this._data_contract.size) { + const fields_unseen = new Set(this._data_contract); + for (let record of this.data) { + Object.keys(record).forEach((field) => fields_unseen.delete(field)); + if (!fields_unseen.size) { + // Once every requested field has been seen in at least one record, no need to look at more records + break; + } + } + if (fields_unseen.size) { + // Current implementation is a soft warning, so that certain "incremental enhancement" features + // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info. + // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data. + console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]} + Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" + If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules. +`); + } + } + this.data.forEach((item, i) => { // Basic toHTML() method - return the stringified value in the id_field, if defined. // When this layer receives data, mark whether points match (via a synthetic boolean field) // Any field-based layout directives (color, size, shape) can then be used to control display if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) { - item.lz_is_match = (match_function(field_resolver.resolve(item), broadcast_value)); + item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value); } - item.toHTML = () => { - const id_field = this.layout.id_field || 'id'; - let html = ''; - if (item[id_field]) { - html = item[id_field].toString(); - } - return html; - }; // Helper methods - return a reference to various plot levels. Useful for interactive tooltips. item.getDataLayer = () => this; item.getPanel = () => this.parent || null; @@ -510,11 +587,6 @@

Source: components/data_layer/base.js

const panel = this.parent; return panel ? panel.parent : null; }; - // deselect() method - shortcut method to deselect the element - item.deselect = () => { - const data_layer = this.getDataLayer(); - data_layer.unselectElement(this); // dynamically generated method name. It exists, honest. - }; }); this.applyCustomDataMethods(); return this; @@ -587,7 +659,6 @@

Source: components/data_layer/base.js

* @param {('x'|'y')} dimension */ getAxisExtent (dimension) { - if (!['x', 'y'].includes(dimension)) { throw new Error('Invalid dimension identifier'); } @@ -647,7 +718,6 @@

Source: components/data_layer/base.js

// No conditions met for generating a valid extent, return an empty array return []; - } /** @@ -866,7 +936,7 @@

Source: components/data_layer/base.js

*/ getElementAnnotation (element, key) { const id = this.getElementId(element); - const extra = this.layer_state.extra_fields[id]; + const extra = this._layer_state.extra_fields[id]; return key ? (extra && extra[key]) : extra; } @@ -905,8 +975,8 @@

Source: components/data_layer/base.js

// Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip), // and "extra fields" (annotations like "show a tooltip" that are not determined by the server, but need to // persist across re-render) - const layer_state = { status_flags: {}, extra_fields: {} }; - const status_flags = layer_state.status_flags; + const _layer_state = { status_flags: {}, extra_fields: {} }; + const status_flags = _layer_state.status_flags; STATUSES.adjectives.forEach((status) => { status_flags[status] = status_flags[status] || new Set(); }); @@ -915,11 +985,11 @@

Source: components/data_layer/base.js

if (this.parent) { // If layer has a parent, store a reference in the overarching plot.state object - this.state_id = `${this.parent.id}.${this.id}`; + this._state_id = `${this.parent.id}.${this.id}`; this.state = this.parent.state; - this.state[this.state_id] = layer_state; + this.state[this._state_id] = _layer_state; } - this.layer_state = layer_state; + this._layer_state = _layer_state; } /** @@ -991,18 +1061,18 @@

Source: components/data_layer/base.js

throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`); } const id = this.getElementId(data); - if (this.tooltips[id]) { + if (this._tooltips[id]) { this.positionTooltip(id); return; } - this.tooltips[id] = { + this._tooltips[id] = { data: data, arrow: null, selector: d3.select(this.parent_plot.svg.node().parentNode).append('div') .attr('class', 'lz-data_layer-tooltip') .attr('id', `${id}-tooltip`), }; - this.layer_state.status_flags['has_tooltip'].add(id); + this._layer_state.status_flags['has_tooltip'].add(id); this.updateTooltip(data); return this; } @@ -1019,16 +1089,16 @@

Source: components/data_layer/base.js

id = this.getElementId(d); } // Empty the tooltip of all HTML (including its arrow!) - this.tooltips[id].selector.html(''); - this.tooltips[id].arrow = null; + this._tooltips[id].selector.html(''); + this._tooltips[id].arrow = null; // Set the new HTML if (this.layout.tooltip.html) { - this.tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d))); + this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d))); } // If the layout allows tool tips on this data layer to be closable then add the close button // and add padding to the tooltip to accommodate it if (this.layout.tooltip.closable) { - this.tooltips[id].selector.insert('button', ':first-child') + this._tooltips[id].selector.insert('button', ':first-child') .attr('class', 'lz-tooltip-close-button') .attr('title', 'Close') .text('×') @@ -1037,7 +1107,7 @@

Source: components/data_layer/base.js

}); } // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip - this.tooltips[id].selector.data([d]); + this._tooltips[id].selector.data([d]); // Reposition and draw a new arrow this.positionTooltip(id); return this; @@ -1059,15 +1129,15 @@

Source: components/data_layer/base.js

} else { id = this.getElementId(element_or_id); } - if (this.tooltips[id]) { - if (typeof this.tooltips[id].selector == 'object') { - this.tooltips[id].selector.remove(); + if (this._tooltips[id]) { + if (typeof this._tooltips[id].selector == 'object') { + this._tooltips[id].selector.remove(); } - delete this.tooltips[id]; + delete this._tooltips[id]; } // When a tooltip is removed, also remove the reference from the state if (!temporary) { - const tooltip_state = this.layer_state.status_flags['has_tooltip']; + const tooltip_state = this._layer_state.status_flags['has_tooltip']; tooltip_state.delete(id); } return this; @@ -1080,7 +1150,7 @@

Source: components/data_layer/base.js

* @returns {BaseDataLayer} */ destroyAllTooltips(temporary = true) { - for (let id in this.tooltips) { + for (let id in this._tooltips) { this.destroyTooltip(id, temporary); } return this; @@ -1101,10 +1171,10 @@

Source: components/data_layer/base.js

if (typeof id != 'string') { throw new Error('Unable to position tooltip: id is not a string'); } - if (!this.tooltips[id]) { + if (!this._tooltips[id]) { throw new Error('Unable to position tooltip: id does not point to a valid tooltip'); } - const tooltip = this.tooltips[id]; + const tooltip = this._tooltips[id]; const coords = this._getTooltipPosition(tooltip); if (!coords) { @@ -1123,7 +1193,7 @@

Source: components/data_layer/base.js

* @returns {BaseDataLayer} */ positionAllTooltips() { - for (let id in this.tooltips) { + for (let id in this._tooltips) { this.positionTooltip(id); } return this; @@ -1139,7 +1209,8 @@

Source: components/data_layer/base.js

* @returns {BaseDataLayer} */ showOrHideTooltip(element, first_time) { - if (typeof this.layout.tooltip != 'object') { + const tooltip_layout = this.layout.tooltip; + if (typeof tooltip_layout != 'object') { return this; } const id = this.getElementId(element); @@ -1190,25 +1261,25 @@

Source: components/data_layer/base.js

}; let show_directive = {}; - if (typeof this.layout.tooltip.show == 'string') { - show_directive = { and: [ this.layout.tooltip.show ] }; - } else if (typeof this.layout.tooltip.show == 'object') { - show_directive = this.layout.tooltip.show; + if (typeof tooltip_layout.show == 'string') { + show_directive = { and: [ tooltip_layout.show ] }; + } else if (typeof tooltip_layout.show == 'object') { + show_directive = tooltip_layout.show; } let hide_directive = {}; - if (typeof this.layout.tooltip.hide == 'string') { - hide_directive = { and: [ this.layout.tooltip.hide ] }; - } else if (typeof this.layout.tooltip.hide == 'object') { - hide_directive = this.layout.tooltip.hide; + if (typeof tooltip_layout.hide == 'string') { + hide_directive = { and: [ tooltip_layout.hide ] }; + } else if (typeof tooltip_layout.hide == 'object') { + hide_directive = tooltip_layout.hide; } // Find all the statuses that apply to just this single element - const layer_state = this.layer_state; + const _layer_state = this._layer_state; var status_flags = {}; // {status_name: bool} STATUSES.adjectives.forEach((status) => { const antistatus = `un${status}`; - status_flags[status] = (layer_state.status_flags[status].has(id)); + status_flags[status] = (_layer_state.status_flags[status].has(id)); status_flags[antistatus] = !status_flags[status]; }); @@ -1219,7 +1290,7 @@

Source: components/data_layer/base.js

// Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc. // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for // some outside reason (like state change), we must track this in the create/destroy events as tooltip state. - const has_tooltip = (layer_state.status_flags['has_tooltip'].has(id)); + const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id)); const tooltip_was_closed = first_time ? false : !has_tooltip; if (show_resolved && !tooltip_was_closed && !hide_resolved) { this.createTooltip(element); @@ -1274,12 +1345,12 @@

Source: components/data_layer/base.js

} // Track element ID in the proper status state array - const added_status = !this.layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied. + const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied. if (active && added_status) { - this.layer_state.status_flags[status].add(element_id); + this._layer_state.status_flags[status].add(element_id); } if (!active && !added_status) { - this.layer_state.status_flags[status].delete(element_id); + this._layer_state.status_flags[status].delete(element_id); } // Trigger tool tip show/hide logic @@ -1322,7 +1393,7 @@

Source: components/data_layer/base.js

if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) { throw new Error('Invalid status'); } - if (typeof this.layer_state.status_flags[status] == 'undefined') { + if (typeof this._layer_state.status_flags[status] == 'undefined') { return this; } if (typeof toggle == 'undefined') { @@ -1333,18 +1404,18 @@

Source: components/data_layer/base.js

if (toggle) { this.data.forEach((element) => this.setElementStatus(status, element, true)); } else { - const status_ids = new Set(this.layer_state.status_flags[status]); // copy so that we don't mutate while iterating + const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating status_ids.forEach((id) => { const element = this.getElementById(id); if (typeof element == 'object' && element !== null) { this.setElementStatus(status, element, false); } }); - this.layer_state.status_flags[status] = new Set(); + this._layer_state.status_flags[status] = new Set(); } // Update global status flag - this.global_statuses[status] = toggle; + this._global_statuses[status] = toggle; return this; } @@ -1423,7 +1494,7 @@

Source: components/data_layer/base.js

// Toggle a status case 'toggle': - var current_status_boolean = (self.layer_state.status_flags[behavior.status].has(self.getElementId(element))); + var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element))); var exclusive = behavior.exclusive && !current_status_boolean; self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive); @@ -1470,7 +1541,7 @@

Source: components/data_layer/base.js

* @private */ applyAllElementStatus () { - const status_flags = this.layer_state.status_flags; + const status_flags = this._layer_state.status_flags; const self = this; for (let property in status_flags) { if (!Object.prototype.hasOwnProperty.call(status_flags, property)) { @@ -1480,7 +1551,7 @@

Source: components/data_layer/base.js

try { this.setElementStatus(property, this.getElementById(element_id), true); } catch (e) { - console.warn(`Unable to apply state: ${self.state_id}, ${property}`); + console.warn(`Unable to apply state: ${self._state_id}, ${property}`); console.error(e); } }); @@ -1516,11 +1587,17 @@

Source: components/data_layer/base.js

// and then recreated if returning to visibility // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads) - return this.parent_plot.lzd.getData(this.state, this.layout.fields) + return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies) .then((new_data) => { - this.data = new_data.body; // chain.body from datasources + this.data = new_data; this.applyDataMethods(); - this.initialized = true; + this._initialized = true; + // Allow listeners (like subscribeToData) to see the information associated with a layer + this.parent.emit( + 'data_from_layer', + { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin + true + ); }); } } @@ -1633,7 +1710,7 @@

Source: components/data_layer/base.js


diff --git a/docs/api/components_data_layer_genes.js.html b/docs/api/components_data_layer_genes.js.html index 5371cc61..e3b46b87 100644 --- a/docs/api/components_data_layer_genes.js.html +++ b/docs/api/components_data_layer_genes.js.html @@ -424,7 +424,7 @@

Source: components/data_layer/genes.js


diff --git a/docs/api/components_data_layer_highlight_regions.js.html b/docs/api/components_data_layer_highlight_regions.js.html index 9e52bca8..8f376abb 100644 --- a/docs/api/components_data_layer_highlight_regions.js.html +++ b/docs/api/components_data_layer_highlight_regions.js.html @@ -84,7 +84,7 @@

Source: components/data_layer/highlight_regions.js

throw new Error('highlight_regions layer does not support mouse events'); } - if (layout.regions && layout.regions.length && layout.fields && layout.fields.length) { + if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) { throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both'); } super(...arguments); @@ -177,7 +177,7 @@

Source: components/data_layer/highlight_regions.js


diff --git a/docs/api/components_data_layer_line.js.html b/docs/api/components_data_layer_line.js.html index 7fe6d3f2..595f63a3 100644 --- a/docs/api/components_data_layer_line.js.html +++ b/docs/api/components_data_layer_line.js.html @@ -45,6 +45,7 @@

Source: components/data_layer/line.js

x_axis: { field: 'x' }, y_axis: { field: 'y', axis: 1 }, hitarea_width: 5, + tooltip: null, }; /********************* @@ -133,7 +134,7 @@

Source: components/data_layer/line.js

if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) { throw new Error('Invalid status'); } - if (typeof this.layer_state.status_flags[status] == 'undefined') { + if (typeof this._layer_state.status_flags[status] == 'undefined') { return this; } if (typeof toggle == 'undefined') { @@ -141,12 +142,12 @@

Source: components/data_layer/line.js

} // Update global status flag - this.global_statuses[status] = toggle; + this._global_statuses[status] = toggle; // Apply class to path based on global status flags let path_class = 'lz-data_layer-line'; - Object.keys(this.global_statuses).forEach((global_status) => { - if (this.global_statuses[global_status]) { + Object.keys(this._global_statuses).forEach((global_status) => { + if (this._global_statuses[global_status]) { path_class += ` lz-data_layer-line-${global_status}`; } }); @@ -206,10 +207,6 @@

Source: components/data_layer/line.js

layout.orientation = 'horizontal'; } super(...arguments); - - // Vars for storing the data generated line - /** @member {Array} */ - this.data = []; } getElementId(element) { @@ -306,7 +303,7 @@

Source: components/data_layer/line.js


diff --git a/docs/api/components_data_layer_scatter.js.html b/docs/api/components_data_layer_scatter.js.html index 21b08a7b..ead23fc6 100644 --- a/docs/api/components_data_layer_scatter.js.html +++ b/docs/api/components_data_layer_scatter.js.html @@ -161,18 +161,18 @@

Source: components/data_layer/scatter.js

}; // Flip any going over the right edge from the right side to the left side // (all labels start on the right side) - data_layer.label_texts.each(function (d, i) { + data_layer._label_texts.each(function (d, i) { const a = this; const da = d3.select(a); const dax = +da.attr('x'); const abound = da.node().getBoundingClientRect(); if (dax + abound.width + spacing > max_x) { - const dal = handle_lines ? d3.select(data_layer.label_lines.nodes()[i]) : null; + const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null; flip(da, dal); } }); // Second pass to flip any others that haven't flipped yet if they collide with another label - data_layer.label_texts.each(function (d, i) { + data_layer._label_texts.each(function (d, i) { const a = this; const da = d3.select(a); if (da.style('text-anchor') === 'end') { @@ -180,8 +180,8 @@

Source: components/data_layer/scatter.js

} let dax = +da.attr('x'); const abound = da.node().getBoundingClientRect(); - const dal = handle_lines ? d3.select(data_layer.label_lines.nodes()[i]) : null; - data_layer.label_texts.each(function () { + const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null; + data_layer._label_texts.each(function () { const b = this; const db = d3.select(b); const bbound = db.node().getBoundingClientRect(); @@ -205,7 +205,7 @@

Source: components/data_layer/scatter.js

// Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/ // TODO: Make labels also aware of data elements separate_labels() { - this.seperate_iterations++; + this._label_iterations++; const data_layer = this; const alpha = 0.5; if (!this.layout.label) { @@ -214,12 +214,12 @@

Source: components/data_layer/scatter.js

} const spacing = this.layout.label.spacing; let again = false; - data_layer.label_texts.each(function () { + data_layer._label_texts.each(function () { // TODO: O(n2) algorithm; revisit performance? const a = this; const da = d3.select(a); const y1 = da.attr('y'); - data_layer.label_texts.each(function () { + data_layer._label_texts.each(function () { const b = this; // a & b are the same element and don't collide. if (a === b) { @@ -278,14 +278,14 @@

Source: components/data_layer/scatter.js

if (again) { // Adjust lines to follow the labels if (data_layer.layout.label.lines) { - const label_elements = data_layer.label_texts.nodes(); - data_layer.label_lines.attr('y2', (d, i) => { + const label_elements = data_layer._label_texts.nodes(); + data_layer._label_lines.attr('y2', (d, i) => { const label_line = d3.select(label_elements[i]); return label_line.attr('y'); }); } // After ~150 iterations we're probably beyond diminising returns, so stop recursing - if (this.seperate_iterations < 150) { + if (this._label_iterations < 150) { setTimeout(() => { this.separate_labels(); }, 1); @@ -341,20 +341,20 @@

Source: components/data_layer/scatter.js

} // Render label groups - this.label_groups = this.svg.group + this._label_groups = this.svg.group .selectAll(`g.lz-data_layer-${this.layout.type}-label`) .data(label_data, (d) => `${d[this.layout.id_field]}_label`); const style_class = `lz-data_layer-${this.layout.type}-label`; - const groups_enter = this.label_groups.enter() + const groups_enter = this._label_groups.enter() .append('g') .attr('class', style_class); - if (this.label_texts) { - this.label_texts.remove(); + if (this._label_texts) { + this._label_texts.remove(); } - this.label_texts = this.label_groups.merge(groups_enter) + this._label_texts = this._label_groups.merge(groups_enter) .append('text') .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d))) .attr('x', (d) => { @@ -368,10 +368,10 @@

Source: components/data_layer/scatter.js

// Render label lines if (data_layer.layout.label.lines) { - if (this.label_lines) { - this.label_lines.remove(); + if (this._label_lines) { + this._label_lines.remove(); } - this.label_lines = this.label_groups.merge(groups_enter) + this._label_lines = this._label_groups.merge(groups_enter) .append('line') .attr('x1', (d) => d[xcs]) .attr('y1', (d) => d[ycs]) @@ -384,18 +384,18 @@

Source: components/data_layer/scatter.js

.call(applyStyles, data_layer.layout.label.lines.style || {}); } // Remove labels when they're no longer in the filtered data set - this.label_groups.exit() + this._label_groups.exit() .remove(); } else { // If the layout definition has changed (& no longer specifies labels), strip any previously rendered - if (this.label_texts) { - this.label_texts.remove(); + if (this._label_texts) { + this._label_texts.remove(); } - if (this.label_lines) { - this.label_lines.remove(); + if (this._label_lines) { + this._label_lines.remove(); } - if (this.label_groups) { - this.label_groups.remove(); + if (this._label_groups) { + this._label_groups.remove(); } } @@ -430,7 +430,7 @@

Source: components/data_layer/scatter.js

// Apply method to keep labels from overlapping each other if (this.layout.label) { this.flip_labels(); - this.seperate_iterations = 0; + this._label_iterations = 0; this.separate_labels(); } @@ -716,7 +716,7 @@

Source: components/data_layer/scatter.js


diff --git a/docs/api/components_legend.js.html b/docs/api/components_legend.js.html index c38a31c7..ce64449e 100644 --- a/docs/api/components_legend.js.html +++ b/docs/api/components_legend.js.html @@ -127,7 +127,7 @@

Source: components/legend.js

let x = padding; let y = padding; let line_height = 0; - this.parent.data_layer_ids_by_z_index.slice().reverse().forEach((id) => { + this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => { if (Array.isArray(this.parent.data_layers[id].layout.legend)) { this.parent.data_layers[id].layout.legend.forEach((element) => { const selector = this.elements_group.append('g') @@ -276,7 +276,7 @@

Source: components/legend.js


diff --git a/docs/api/components_panel.js.html b/docs/api/components_panel.js.html index b7850410..14d82a63 100644 --- a/docs/api/components_panel.js.html +++ b/docs/api/components_panel.js.html @@ -87,7 +87,7 @@

Source: components/panel.js

*/ class Panel { /** - * @param {string} [layout.id=''] An identifier string that must be unique across all panels in the plot + * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required. * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like "find every panel * that shows association scatter plots, anywhere": even if the IDs are different, the tag can be the same. @@ -165,20 +165,8 @@

Source: components/panel.js

*/ this.parent_plot = parent; - // Ensure a valid ID is present. If there is no valid ID then generate one - if (typeof layout.id !== 'string' || !layout.id.length) { - if (!this.parent) { - layout.id = `p${Math.floor(Math.random() * Math.pow(10, 8))}`; - } else { - const generateID = () => { - let id = `p${Math.floor(Math.random() * Math.pow(10, 8))}`; - if (id === null || typeof this.parent.panels[id] != 'undefined') { - id = generateID(); - } - return id; - }; - layout.id = generateID(); - } + if (typeof layout.id !== 'string' || !layout.id) { + throw new Error('Panel layouts must specify "id"'); } else if (this.parent) { if (typeof this.parent.panels[layout.id] !== 'undefined') { throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`); @@ -194,13 +182,13 @@

Source: components/panel.js

* @private * @member {Boolean} */ - this.initialized = false; + this._initialized = false; /** * The index of this panel in the parent plot's `layout.panels` * @private * @member {number} * */ - this.layout_idx = null; + this._layout_idx = null; /** * @private * @member {Object} @@ -226,11 +214,11 @@

Source: components/panel.js

* @private * @member {String} */ - this.state_id = this.id; - this.state[this.state_id] = this.state[this.state_id] || {}; + this._state_id = this.id; + this.state[this._state_id] = this.state[this._state_id] || {}; } else { this.state = null; - this.state_id = null; + this._state_id = null; } /** @@ -243,14 +231,14 @@

Source: components/panel.js

* @private * @member {String[]} */ - this.data_layer_ids_by_z_index = []; + this._data_layer_ids_by_z_index = []; /** * Track data requests in progress * @member {Promise[]} * @private */ - this.data_promises = []; + this._data_promises = []; /** * @private @@ -305,7 +293,7 @@

Source: components/panel.js

* @private * @member {number} */ - this.zoom_timeout = null; + this._zoom_timeout = null; /** * Known event hooks that the panel can respond to @@ -313,7 +301,7 @@

Source: components/panel.js

* @protected * @member {Object} */ - this.event_hooks = {}; + this._event_hooks = {}; // Initialize the layout this.initializeLayout(); @@ -344,11 +332,11 @@

Source: components/panel.js

if (typeof hook != 'function') { throw new Error('Unable to register event hook, invalid hook function passed'); } - if (!this.event_hooks[event]) { + if (!this._event_hooks[event]) { // We do not validate on known event names, because LZ is allowed to track and emit custom events like "widget button clicked". - this.event_hooks[event] = []; + this._event_hooks[event] = []; } - this.event_hooks[event].push(hook); + this._event_hooks[event].push(hook); return hook; } @@ -360,14 +348,14 @@

Source: components/panel.js

* @returns {Panel} */ off(event, hook) { - const theseHooks = this.event_hooks[event]; + const theseHooks = this._event_hooks[event]; if (typeof event != 'string' || !Array.isArray(theseHooks)) { throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`); } if (hook === undefined) { // Deregistering all hooks for this event may break basic functionality, and should only be used during // cleanup operations (eg to prevent memory leaks) - this.event_hooks[event] = []; + this._event_hooks[event] = []; } else { const hookMatch = theseHooks.indexOf(hook); if (hookMatch !== -1) { @@ -408,9 +396,9 @@

Source: components/panel.js

const sourceID = this.getBaseId(); const eventContext = { sourceID: sourceID, target: this, data: eventData || null }; - if (this.event_hooks[event]) { + if (this._event_hooks[event]) { // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound? - this.event_hooks[event].forEach((hookToRun) => { + this._event_hooks[event].forEach((hookToRun) => { // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is // registered as a handler, the previously bound `this` will override anything provided to `call` below. hookToRun.call(this, eventContext); @@ -479,7 +467,7 @@

Source: components/panel.js

throw new Error('Invalid data layer layout'); } if (typeof this.data_layers[layout.id] !== 'undefined') { - throw new Error(`Cannot create data_layer with id [${layout.id}]; data layer with that id already exists in the panel`); + throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`); } if (typeof layout.type !== 'string') { throw new Error('Invalid data layer type'); @@ -498,17 +486,17 @@

Source: components/panel.js

// If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index) - && this.data_layer_ids_by_z_index.length > 0) { + && this._data_layer_ids_by_z_index.length > 0) { // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here if (data_layer.layout.z_index < 0) { - data_layer.layout.z_index = Math.max(this.data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0); + data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0); } - this.data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id); - this.data_layer_ids_by_z_index.forEach((dlid, idx) => { + this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id); + this._data_layer_ids_by_z_index.forEach((dlid, idx) => { this.data_layers[dlid].layout.z_index = idx; }); } else { - const length = this.data_layer_ids_by_z_index.push(data_layer.id); + const length = this._data_layer_ids_by_z_index.push(data_layer.id); this.data_layers[data_layer.id].layout.z_index = length - 1; } @@ -523,7 +511,7 @@

Source: components/panel.js

if (layout_idx === null) { layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1; } - this.data_layers[data_layer.id].layout_idx = layout_idx; + this.data_layers[data_layer.id]._layout_idx = layout_idx; return this.data_layers[data_layer.id]; } @@ -535,30 +523,31 @@

Source: components/panel.js

* @returns {Panel} */ removeDataLayer(id) { - if (!this.data_layers[id]) { + const target_layer = this.data_layers[id]; + if (!target_layer) { throw new Error(`Unable to remove data layer, ID not found: ${id}`); } // Destroy all tooltips for the data layer - this.data_layers[id].destroyAllTooltips(); + target_layer.destroyAllTooltips(); // Remove the svg container for the data layer if it exists - if (this.data_layers[id].svg.container) { - this.data_layers[id].svg.container.remove(); + if (target_layer.svg.container) { + target_layer.svg.container.remove(); } // Delete the data layer and its presence in the panel layout and state - this.layout.data_layers.splice(this.data_layers[id].layout_idx, 1); - delete this.state[this.data_layers[id].state_id]; + this.layout.data_layers.splice(target_layer._layout_idx, 1); + delete this.state[target_layer._state_id]; delete this.data_layers[id]; // Remove the data_layer id from the z_index array - this.data_layer_ids_by_z_index.splice(this.data_layer_ids_by_z_index.indexOf(id), 1); + this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1); // Update layout_idx and layout.z_index values for all remaining data_layers this.applyDataLayerZIndexesToDataLayerLayouts(); this.layout.data_layers.forEach((data_layer_layout, idx) => { - this.data_layers[data_layer_layout.id].layout_idx = idx; + this.data_layers[data_layer_layout.id]._layout_idx = idx; }); return this; @@ -570,7 +559,7 @@

Source: components/panel.js

* @returns {Panel} */ clearSelections() { - this.data_layer_ids_by_z_index.forEach((id) => { + this._data_layer_ids_by_z_index.forEach((id) => { this.data_layers[id].setAllElementStatus('selected', false); }); return this; @@ -583,7 +572,6 @@

Source: components/panel.js

* @returns {Panel} */ render() { - // Position the panel container this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`); @@ -592,12 +580,15 @@

Source: components/panel.js

.attr('width', this.parent_plot.layout.width) .attr('height', this.layout.height); + const { cliparea } = this.layout; + // Set and position the inner border, style if necessary + const { margin } = this.layout; this.inner_border - .attr('x', this.layout.margin.left) - .attr('y', this.layout.margin.top) - .attr('width', this.parent_plot.layout.width - (this.layout.margin.left + this.layout.margin.right)) - .attr('height', this.layout.height - (this.layout.margin.top + this.layout.margin.bottom)); + .attr('x', margin.left) + .attr('y', margin.top) + .attr('width', this.parent_plot.layout.width - (margin.left + margin.right)) + .attr('height', this.layout.height - (margin.top + margin.bottom)); if (this.layout.inner_border) { this.inner_border .style('stroke-width', 1) @@ -637,41 +628,44 @@

Source: components/panel.js

// Define default and shifted ranges for all axes const ranges = {}; + const axes_config = this.layout.axes; if (this.x_extent) { const base_x_range = { start: 0, end: this.layout.cliparea.width }; - if (this.layout.axes.x.range) { - base_x_range.start = this.layout.axes.x.range.start || base_x_range.start; - base_x_range.end = this.layout.axes.x.range.end || base_x_range.end; + if (axes_config.x.range) { + base_x_range.start = axes_config.x.range.start || base_x_range.start; + base_x_range.end = axes_config.x.range.end || base_x_range.end; } ranges.x = [base_x_range.start, base_x_range.end]; ranges.x_shifted = [base_x_range.start, base_x_range.end]; } if (this.y1_extent) { - const base_y1_range = { start: this.layout.cliparea.height, end: 0 }; - if (this.layout.axes.y1.range) { - base_y1_range.start = this.layout.axes.y1.range.start || base_y1_range.start; - base_y1_range.end = this.layout.axes.y1.range.end || base_y1_range.end; + const base_y1_range = { start: cliparea.height, end: 0 }; + if (axes_config.y1.range) { + base_y1_range.start = axes_config.y1.range.start || base_y1_range.start; + base_y1_range.end = axes_config.y1.range.end || base_y1_range.end; } ranges.y1 = [base_y1_range.start, base_y1_range.end]; ranges.y1_shifted = [base_y1_range.start, base_y1_range.end]; } if (this.y2_extent) { - const base_y2_range = { start: this.layout.cliparea.height, end: 0 }; - if (this.layout.axes.y2.range) { - base_y2_range.start = this.layout.axes.y2.range.start || base_y2_range.start; - base_y2_range.end = this.layout.axes.y2.range.end || base_y2_range.end; + const base_y2_range = { start: cliparea.height, end: 0 }; + if (axes_config.y2.range) { + base_y2_range.start = axes_config.y2.range.start || base_y2_range.start; + base_y2_range.end = axes_config.y2.range.end || base_y2_range.end; } ranges.y2 = [base_y2_range.start, base_y2_range.end]; ranges.y2_shifted = [base_y2_range.start, base_y2_range.end]; } // Shift ranges based on any drag or zoom interactions currently underway - if (this.parent.interaction.panel_id && (this.parent.interaction.panel_id === this.id || this.parent.interaction.linked_panel_ids.includes(this.id))) { + let { _interaction } = this.parent; + const current_drag = _interaction.dragging; + if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) { let anchor, scalar = null; - if (this.parent.interaction.zooming && typeof this.x_scale == 'function') { + if (_interaction.zooming && typeof this.x_scale == 'function') { const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]); const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0])); - let zoom_factor = this.parent.interaction.zooming.scale; + let zoom_factor = _interaction.zooming.scale; const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor)); if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) { zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size); @@ -679,38 +673,38 @@

Source: components/panel.js

zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size); } const new_extent_size = Math.floor(current_extent_size * zoom_factor); - anchor = this.parent.interaction.zooming.center - this.layout.margin.left - this.layout.origin.x; - const offset_ratio = anchor / this.layout.cliparea.width; + anchor = _interaction.zooming.center - margin.left - this.layout.origin.x; + const offset_ratio = anchor / cliparea.width; const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1); ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ]; - } else if (this.parent.interaction.dragging) { - switch (this.parent.interaction.dragging.method) { + } else if (current_drag) { + switch (current_drag.method) { case 'background': - ranges.x_shifted[0] = +this.parent.interaction.dragging.dragged_x; - ranges.x_shifted[1] = this.layout.cliparea.width + this.parent.interaction.dragging.dragged_x; + ranges.x_shifted[0] = +current_drag.dragged_x; + ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x; break; case 'x_tick': if (d3.event && d3.event.shiftKey) { - ranges.x_shifted[0] = +this.parent.interaction.dragging.dragged_x; - ranges.x_shifted[1] = this.layout.cliparea.width + this.parent.interaction.dragging.dragged_x; + ranges.x_shifted[0] = +current_drag.dragged_x; + ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x; } else { - anchor = this.parent.interaction.dragging.start_x - this.layout.margin.left - this.layout.origin.x; - scalar = constrain(anchor / (anchor + this.parent.interaction.dragging.dragged_x), 3); + anchor = current_drag.start_x - margin.left - this.layout.origin.x; + scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3); ranges.x_shifted[0] = 0; - ranges.x_shifted[1] = Math.max(this.layout.cliparea.width * (1 / scalar), 1); + ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1); } break; case 'y1_tick': case 'y2_tick': { - const y_shifted = `y${this.parent.interaction.dragging.method[1]}_shifted`; + const y_shifted = `y${current_drag.method[1]}_shifted`; if (d3.event && d3.event.shiftKey) { - ranges[y_shifted][0] = this.layout.cliparea.height + this.parent.interaction.dragging.dragged_y; - ranges[y_shifted][1] = +this.parent.interaction.dragging.dragged_y; + ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y; + ranges[y_shifted][1] = +current_drag.dragged_y; } else { - anchor = this.layout.cliparea.height - (this.parent.interaction.dragging.start_y - this.layout.margin.top - this.layout.origin.y); - scalar = constrain(anchor / (anchor - this.parent.interaction.dragging.dragged_y), 3); - ranges[y_shifted][0] = this.layout.cliparea.height; - ranges[y_shifted][1] = this.layout.cliparea.height - (this.layout.cliparea.height * (1 / scalar)); + anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y); + scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3); + ranges[y_shifted][0] = cliparea.height; + ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar)); } } } @@ -762,7 +756,7 @@

Source: components/panel.js

if (delta === 0) { return; } - this.parent.interaction = { + this.parent._interaction = { panel_id: this.id, linked_panel_ids: this.getLinkedPanelIds('x'), zooming: { @@ -771,14 +765,16 @@

Source: components/panel.js

}, }; this.render(); - this.parent.interaction.linked_panel_ids.forEach((panel_id) => { + // Redefine b/c might have been changed during call to parent re-render + _interaction = this.parent._interaction; + _interaction.linked_panel_ids.forEach((panel_id) => { this.parent.panels[panel_id].render(); }); - if (this.zoom_timeout !== null) { - clearTimeout(this.zoom_timeout); + if (this._zoom_timeout !== null) { + clearTimeout(this._zoom_timeout); } - this.zoom_timeout = setTimeout(() => { - this.parent.interaction = {}; + this._zoom_timeout = setTimeout(() => { + this.parent._interaction = {}; this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] }); }, 500); }; @@ -790,7 +786,7 @@

Source: components/panel.js

} // Render data layers in order by z-index - this.data_layer_ids_by_z_index.forEach((data_layer_id) => { + this._data_layer_ids_by_z_index.forEach((data_layer_id) => { this.data_layers[data_layer_id].draw().render(); }); @@ -816,7 +812,7 @@

Source: components/panel.js

* @returns {Panel} */ addBasicLoader(show_immediately = true) { - if (this.layout.show_loading_indicator && this.initialized) { + if (this.layout.show_loading_indicator && this._initialized) { // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default. // Some older pages could thus end up adding a loader twice: to avoid duplicate render events, // short-circuit if a loader is already present after the first render has finished. @@ -840,7 +836,7 @@

Source: components/panel.js

/************* Private interface: only used internally */ /** @private */ applyDataLayerZIndexesToDataLayerLayouts () { - this.data_layer_ids_by_z_index.forEach((dlid, idx) => { + this._data_layer_ids_by_z_index.forEach((dlid, idx) => { this.data_layers[dlid].layout.z_index = idx; }); } @@ -886,13 +882,14 @@

Source: components/panel.js

this.y2_range = [this.layout.cliparea.height, 0]; // Initialize panel axes - ['x', 'y1', 'y2'].forEach((axis) => { - if (!Object.keys(this.layout.axes[axis]).length || this.layout.axes[axis].render === false) { + ['x', 'y1', 'y2'].forEach((id) => { + const axis = this.layout.axes[id]; + if (!Object.keys(axis).length || axis.render === false) { // The default layout sets the axis to an empty object, so set its render boolean here - this.layout.axes[axis].render = false; + axis.render = false; } else { - this.layout.axes[axis].render = true; - this.layout.axes[axis].label = this.layout.axes[axis].label || null; + axis.render = true; + axis.label = axis.label || null; } }); @@ -915,21 +912,22 @@

Source: components/panel.js

* @returns {Panel} */ setDimensions(width, height) { + const layout = this.layout; if (typeof width != 'undefined' && typeof height != 'undefined') { if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) { this.parent.layout.width = Math.round(+width); // Ensure that the requested height satisfies all minimum values - this.layout.height = Math.max(Math.round(+height), this.layout.min_height); + layout.height = Math.max(Math.round(+height), layout.min_height); } } - this.layout.cliparea.width = Math.max(this.parent_plot.layout.width - (this.layout.margin.left + this.layout.margin.right), 0); - this.layout.cliparea.height = Math.max(this.layout.height - (this.layout.margin.top + this.layout.margin.bottom), 0); + layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0); + layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0); if (this.svg.clipRect) { this.svg.clipRect .attr('width', this.parent.layout.width) - .attr('height', this.layout.height); + .attr('height', layout.height); } - if (this.initialized) { + if (this._initialized) { this.render(); this.curtain.update(); this.loader.update(); @@ -956,7 +954,7 @@

Source: components/panel.js

if (!isNaN(y) && y >= 0) { this.layout.origin.y = Math.max(Math.round(+y), 0); } - if (this.initialized) { + if (this._initialized) { this.render(); } return this; @@ -973,38 +971,39 @@

Source: components/panel.js

*/ setMargin(top, right, bottom, left) { let extra; - if (!isNaN(top) && top >= 0) { - this.layout.margin.top = Math.max(Math.round(+top), 0); + const { cliparea, margin } = this.layout; + if (!isNaN(top) && top >= 0) { + margin.top = Math.max(Math.round(+top), 0); } if (!isNaN(right) && right >= 0) { - this.layout.margin.right = Math.max(Math.round(+right), 0); + margin.right = Math.max(Math.round(+right), 0); } if (!isNaN(bottom) && bottom >= 0) { - this.layout.margin.bottom = Math.max(Math.round(+bottom), 0); + margin.bottom = Math.max(Math.round(+bottom), 0); } if (!isNaN(left) && left >= 0) { - this.layout.margin.left = Math.max(Math.round(+left), 0); + margin.left = Math.max(Math.round(+left), 0); } // If the specified margins are greater than the available width, then shrink the margins. - if (this.layout.margin.top + this.layout.margin.bottom > this.layout.height) { - extra = Math.floor(((this.layout.margin.top + this.layout.margin.bottom) - this.layout.height) / 2); - this.layout.margin.top -= extra; - this.layout.margin.bottom -= extra; + if (margin.top + margin.bottom > this.layout.height) { + extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2); + margin.top -= extra; + margin.bottom -= extra; } - if (this.layout.margin.left + this.layout.margin.right > this.parent_plot.layout.width) { - extra = Math.floor(((this.layout.margin.left + this.layout.margin.right) - this.parent_plot.layout.width) / 2); - this.layout.margin.left -= extra; - this.layout.margin.right -= extra; + if (margin.left + margin.right > this.parent_plot.layout.width) { + extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2); + margin.left -= extra; + margin.right -= extra; } ['top', 'right', 'bottom', 'left'].forEach((m) => { - this.layout.margin[m] = Math.max(this.layout.margin[m], 0); + margin[m] = Math.max(margin[m], 0); }); - this.layout.cliparea.width = Math.max(this.parent_plot.layout.width - (this.layout.margin.left + this.layout.margin.right), 0); - this.layout.cliparea.height = Math.max(this.layout.height - (this.layout.margin.top + this.layout.margin.bottom), 0); - this.layout.cliparea.origin.x = this.layout.margin.left; - this.layout.cliparea.origin.y = this.layout.margin.top; + cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0); + cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0); + cliparea.origin.x = margin.left; + cliparea.origin.y = margin.top; - if (this.initialized) { + if (this._initialized) { this.render(); } return this; @@ -1106,7 +1105,7 @@

Source: components/panel.js

} // Initialize child Data Layers - this.data_layer_ids_by_z_index.forEach((id) => { + this._data_layer_ids_by_z_index.forEach((id) => { this.data_layers[id].initialize(); }); @@ -1138,7 +1137,7 @@

Source: components/panel.js

*/ resortDataLayers() { const sort = []; - this.data_layer_ids_by_z_index.forEach((id) => { + this._data_layer_ids_by_z_index.forEach((id) => { sort.push(this.data_layers[id].layout.z_index); }); this.svg.group @@ -1163,7 +1162,7 @@

Source: components/panel.js

if (!this.layout.interaction[`${axis}_linked`]) { return linked_panel_ids; } - this.parent.panel_ids_by_y_index.forEach((panel_id) => { + this.parent._panel_ids_by_y_index.forEach((panel_id) => { if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) { linked_panel_ids.push(panel_id); } @@ -1177,11 +1176,13 @@

Source: components/panel.js

* @returns {Panel} */ moveUp() { - if (this.parent.panel_ids_by_y_index[this.layout.y_index - 1]) { - this.parent.panel_ids_by_y_index[this.layout.y_index] = this.parent.panel_ids_by_y_index[this.layout.y_index - 1]; - this.parent.panel_ids_by_y_index[this.layout.y_index - 1] = this.id; - this.parent.applyPanelYIndexesToPanelLayouts(); - this.parent.positionPanels(); + const { parent } = this; + const y_index = this.layout.y_index; + if (parent._panel_ids_by_y_index[y_index - 1]) { + parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1]; + parent._panel_ids_by_y_index[y_index - 1] = this.id; + parent.applyPanelYIndexesToPanelLayouts(); + parent.positionPanels(); } return this; } @@ -1192,9 +1193,10 @@

Source: components/panel.js

* @returns {Panel} */ moveDown() { - if (this.parent.panel_ids_by_y_index[this.layout.y_index + 1]) { - this.parent.panel_ids_by_y_index[this.layout.y_index] = this.parent.panel_ids_by_y_index[this.layout.y_index + 1]; - this.parent.panel_ids_by_y_index[this.layout.y_index + 1] = this.id; + const { _panel_ids_by_y_index } = this.parent; + if (_panel_ids_by_y_index[this.layout.y_index + 1]) { + _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1]; + _panel_ids_by_y_index[this.layout.y_index + 1] = this.id; this.parent.applyPanelYIndexesToPanelLayouts(); this.parent.positionPanels(); } @@ -1212,23 +1214,23 @@

Source: components/panel.js

*/ reMap() { this.emit('data_requested'); - this.data_promises = []; + this._data_promises = []; // Remove any previous error messages before attempting to load new data this.curtain.hide(); // Trigger reMap on each Data Layer for (let id in this.data_layers) { try { - this.data_promises.push(this.data_layers[id].reMap()); + this._data_promises.push(this.data_layers[id].reMap()); } catch (error) { console.error(error); this.curtain.show(error.message || error); } } // When all finished trigger a render - return Promise.all(this.data_promises) + return Promise.all(this._data_promises) .then(() => { - this.initialized = true; + this._initialized = true; this.render(); this.emit('layout_changed', true); this.emit('data_rendered'); @@ -1245,7 +1247,6 @@

Source: components/panel.js

* @returns {Panel} */ generateExtents() { - // Reset extents ['x', 'y1', 'y2'].forEach((axis) => { this[`${axis}_extent`] = null; @@ -1253,7 +1254,6 @@

Source: components/panel.js

// Loop through the data layers for (let id in this.data_layers) { - const data_layer = this.data_layers[id]; // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent @@ -1295,7 +1295,6 @@

Source: components/panel.js

* * color: string or LocusZoom scalable parameter object */ generateTicks(axis) { - // Parse an explicit 'ticks' attribute in the axis layout if (this.layout.axes[axis].ticks) { const layout = this.layout.axes[axis]; @@ -1315,7 +1314,7 @@

Source: components/panel.js

// Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately) const config = { position: baseTickConfig.position }; - const combinedTicks = this.data_layer_ids_by_z_index.reduce((acc, data_layer_id) => { + const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => { const nextLayer = self.data_layers[data_layer_id]; return acc.concat(nextLayer.getTicks(axis, config)); }, []); @@ -1516,7 +1515,7 @@

Source: components/panel.js

scaleHeightToData(target_height) { target_height = +target_height || null; if (target_height === null) { - this.data_layer_ids_by_z_index.forEach((id) => { + this._data_layer_ids_by_z_index.forEach((id) => { const dh = this.data_layers[id].getAbsoluteDataHeight(); if (+dh) { if (target_height === null) { @@ -1543,7 +1542,7 @@

Source: components/panel.js

* @param {Boolean} toggle */ setAllElementStatus(status, toggle) { - this.data_layer_ids_by_z_index.forEach((id) => { + this._data_layer_ids_by_z_index.forEach((id) => { this.data_layers[id].setAllElementStatus(status, toggle); }); } @@ -1608,7 +1607,7 @@

Source: components/panel.js


diff --git a/docs/api/components_plot.js.html b/docs/api/components_plot.js.html index 519f8a75..368ca5c1 100644 --- a/docs/api/components_plot.js.html +++ b/docs/api/components_plot.js.html @@ -94,6 +94,17 @@

Source: components/plot.js

* @see event:any_lz_event */ +/** + * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future. + * @event data_from_layer + * @property {object} data + * @property {String} data.layer The fully qualified ID of the layer emitting this event + * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum + * element. It reflects all namespaces and data operations used by that layer. + * @see event:any_lz_event + */ + + /** * An action occurred that changed, or could change, the layout. * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight, @@ -247,7 +258,7 @@

Source: components/plot.js

* @private * @member Boolean} */ - this.initialized = false; + this._initialized = false; /** * @private @@ -284,7 +295,7 @@

Source: components/plot.js

* @private * @member {String[]} */ - this.panel_ids_by_y_index = []; + this._panel_ids_by_y_index = []; /** * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete @@ -293,7 +304,7 @@

Source: components/plot.js

* @protected * @member {Promise[]} */ - this.remap_promises = []; + this._remap_promises = []; /** @@ -344,7 +355,7 @@

Source: components/plot.js

* @protected * @member {Object} */ - this.event_hooks = {}; + this._event_hooks = {}; /** * @callback eventCallback @@ -362,7 +373,7 @@

Source: components/plot.js

* @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}} * @returns {Plot} */ - this.interaction = {}; + this._interaction = {}; // Initialize the layout this.initializeLayout(); @@ -392,11 +403,11 @@

Source: components/plot.js

if (typeof hook != 'function') { throw new Error('Unable to register event hook, invalid hook function passed'); } - if (!this.event_hooks[event]) { + if (!this._event_hooks[event]) { // We do not validate on known event names, because LZ is allowed to track and emit custom events like "widget button clicked". - this.event_hooks[event] = []; + this._event_hooks[event] = []; } - this.event_hooks[event].push(hook); + this._event_hooks[event].push(hook); return hook; } @@ -409,14 +420,14 @@

Source: components/plot.js

* @returns {Plot} */ off(event, hook) { - const theseHooks = this.event_hooks[event]; + const theseHooks = this._event_hooks[event]; if (typeof event != 'string' || !Array.isArray(theseHooks)) { throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`); } if (hook === undefined) { // Deregistering all hooks for this event may break basic functionality, and should only be used during // cleanup operations (eg to prevent memory leaks) - this.event_hooks[event] = []; + this._event_hooks[event] = []; } else { const hookMatch = theseHooks.indexOf(hook); if (hookMatch !== -1) { @@ -439,10 +450,10 @@

Source: components/plot.js

emit(event, eventData) { // TODO: there are small differences between the emit implementation between plots and panels. In the future, // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring. - const these_hooks = this.event_hooks[event]; + const these_hooks = this._event_hooks[event]; if (typeof event != 'string') { throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`); - } else if (!these_hooks && !this.event_hooks['any_lz_event']) { + } else if (!these_hooks && !this._event_hooks['any_lz_event']) { // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound? return this; } @@ -496,15 +507,15 @@

Source: components/plot.js

// If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index) - && this.panel_ids_by_y_index.length > 0) { + && this._panel_ids_by_y_index.length > 0) { // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here if (panel.layout.y_index < 0) { - panel.layout.y_index = Math.max(this.panel_ids_by_y_index.length + panel.layout.y_index, 0); + panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0); } - this.panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id); + this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id); this.applyPanelYIndexesToPanelLayouts(); } else { - const length = this.panel_ids_by_y_index.push(panel.id); + const length = this._panel_ids_by_y_index.push(panel.id); this.panels[panel.id].layout.y_index = length - 1; } @@ -519,10 +530,10 @@

Source: components/plot.js

if (layout_idx === null) { layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1; } - this.panels[panel.id].layout_idx = layout_idx; + this.panels[panel.id]._layout_idx = layout_idx; // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space - if (this.initialized) { + if (this._initialized) { this.positionPanels(); // Initialize and load data into the new panel this.panels[panel.id].initialize(); @@ -559,12 +570,12 @@

Source: components/plot.js

} panelsList.forEach((pid) => { - this.panels[pid].data_layer_ids_by_z_index.forEach((dlid) => { + this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => { const layer = this.panels[pid].data_layers[dlid]; layer.destroyAllTooltips(); - delete layer.layer_state; - delete this.layout.state[layer.state_id]; + delete layer._layer_state; + delete this.layout.state[layer._state_id]; if (mode === 'reset') { layer._setDefaultState(); } @@ -581,42 +592,43 @@

Source: components/plot.js

* @returns {Plot} */ removePanel(id) { - if (!this.panels[id]) { + const target_panel = this.panels[id]; + if (!target_panel) { throw new Error(`Unable to remove panel, ID not found: ${id}`); } // Hide all panel boundaries - this.panel_boundaries.hide(); + this._panel_boundaries.hide(); // Destroy all tooltips and state vars for all data layers on the panel this.clearPanelData(id); // Remove all panel-level HTML overlay elements - this.panels[id].loader.hide(); - this.panels[id].toolbar.destroy(true); - this.panels[id].curtain.hide(); + target_panel.loader.hide(); + target_panel.toolbar.destroy(true); + target_panel.curtain.hide(); // Remove the svg container for the panel if it exists - if (this.panels[id].svg.container) { - this.panels[id].svg.container.remove(); + if (target_panel.svg.container) { + target_panel.svg.container.remove(); } // Delete the panel and its presence in the plot layout and state - this.layout.panels.splice(this.panels[id].layout_idx, 1); + this.layout.panels.splice(target_panel._layout_idx, 1); delete this.panels[id]; delete this.layout.state[id]; // Update layout_idx values for all remaining panels this.layout.panels.forEach((panel_layout, idx) => { - this.panels[panel_layout.id].layout_idx = idx; + this.panels[panel_layout.id]._layout_idx = idx; }); // Remove the panel id from the y_index array - this.panel_ids_by_y_index.splice(this.panel_ids_by_y_index.indexOf(id), 1); + this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1); this.applyPanelYIndexesToPanelLayouts(); // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space - if (this.initialized) { + if (this._initialized) { this.positionPanels(); // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip // positioning. TODO: make this additional call unnecessary. @@ -642,6 +654,8 @@

Source: components/plot.js

* @callback externalDataCallback * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to * a data layer making an equivalent request. + * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the + * structure of the data, instead of just displaying something. */ /** @@ -659,30 +673,74 @@

Source: components/plot.js

* * @public * @listens event:data_rendered - * @param {String[]} fields An array of field names and transforms, in the same syntax used by a data layer. - * Different data sources should be prefixed by the namespace name. - * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that - * new data is received by the plot. Receives two arguments: (data, plot). + * @listens event:data_from_layer * @param {Object} [opts] Options + * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched. + * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details. + * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested, + * this is usually required in order to specify dependency order and join operations. See data layer documentation for details. * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem * occurs during the data request or subsequent callback operations - * @param {boolean} [opts.discrete=false] Normally the callback will subscribe to the combined body from the chain, - * which may not be in a format that matches what the external callback wants to do. If discrete=true, returns the - * uncombined record info + * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that + * new data is received by the plot. Receives two arguments: (data, plot). * @return {function} The newly created event listener, to allow for later cleanup/removal */ - subscribeToData(fields, success_callback, opts) { - opts = opts || {}; + subscribeToData(opts, success_callback) { + const { from_layer, namespace, data_operations, onerror } = opts; // Register an event listener that is notified whenever new data has been rendered - const error_callback = opts.onerror || function (err) { - console.log('An error occurred while acting on an external callback', err); + const error_callback = onerror || function (err) { + console.error('An error occurred while acting on an external callback', err); }; + if (from_layer) { + // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places. + const base_prefix = `${this.getBaseId()}.`; + // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code. + const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`; + + // Ensure that a valid layer exists to watch + let is_valid_layer = false; + for (let p of Object.values(this.panels)) { + is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target); + if (is_valid_layer) { + break; + } + } + if (!is_valid_layer) { + throw new Error(`Could not subscribe to unknown data layer ${layer_target}`); + } + + const listener = (eventData) => { + if (eventData.data.layer !== layer_target) { + // Same event name fires for many layers; only fire success cb for the one layer we want + return; + } + try { + success_callback(eventData.data.content, this); + } catch (error) { + error_callback(error); + } + }; + + this.on('data_from_layer', listener); + return listener; + } + + // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of + // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized + // in connection to some other visualization) + if (!namespace) { + throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option"); + } + + const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot. const listener = () => { try { - this.lzd.getData(this.state, fields) - .then((new_data) => success_callback(opts.discrete ? new_data.discrete : new_data.body, this)) + // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely, + // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame. + this.lzd.getData(this.state, entities, dependencies) + .then((new_data) => success_callback(new_data, this)) .catch(error_callback); } catch (error) { // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up @@ -725,13 +783,13 @@

Source: components/plot.js

// Generate requests for all panels given new state this.emit('data_requested'); - this.remap_promises = []; + this._remap_promises = []; this.loading_data = true; for (let id in this.panels) { - this.remap_promises.push(this.panels[id].reMap()); + this._remap_promises.push(this.panels[id].reMap()); } - return Promise.all(this.remap_promises) + return Promise.all(this._remap_promises) .catch((error) => { console.error(error); this.curtain.show(error.message || error); @@ -742,11 +800,11 @@

Source: components/plot.js

this.toolbar.update(); // Apply panel-level state values - this.panel_ids_by_y_index.forEach((panel_id) => { + this._panel_ids_by_y_index.forEach((panel_id) => { const panel = this.panels[panel_id]; panel.toolbar.update(); // Apply data-layer-level state values - panel.data_layer_ids_by_z_index.forEach((data_layer_id) => { + panel._data_layer_ids_by_z_index.forEach((data_layer_id) => { panel.data_layers[data_layer_id].applyAllElementStatus(); }); }); @@ -820,12 +878,24 @@

Source: components/plot.js

// eslint-disable-next-line no-self-assign parent.outerHTML = parent.outerHTML; - this.initialized = false; + this._initialized = false; this.svg = null; this.panels = null; } + /** + * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another, + * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties, + * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself. + * @public + */ + mutateLayout() { + Object.values(this.panels).forEach((panel) => { + Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout()); + }); + } + /******* The private interface: methods only used by LocusZoom internals */ /** * Track whether the target panel can respond to mouse interaction events @@ -835,10 +905,11 @@

Source: components/plot.js

*/ _canInteract(panel_id) { panel_id = panel_id || null; + const { _interaction } = this; if (panel_id) { - return ((typeof this.interaction.panel_id == 'undefined' || this.interaction.panel_id === panel_id) && !this.loading_data); + return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data); } else { - return !(this.interaction.dragging || this.interaction.zooming || this.loading_data); + return !(_interaction.dragging || _interaction.zooming || this.loading_data); } } @@ -893,7 +964,7 @@

Source: components/plot.js

* @private */ applyPanelYIndexesToPanelLayouts () { - this.panel_ids_by_y_index.forEach((pid, idx) => { + this._panel_ids_by_y_index.forEach((pid, idx) => { this.panels[pid].layout.y_index = idx; }); } @@ -924,7 +995,6 @@

Source: components/plot.js

* @returns {Plot} */ initializeLayout() { - // Sanity check layout values if (isNaN(this.layout.width) || this.layout.width <= 0) { throw new Error('Plot layout parameter `width` must be a positive number'); @@ -933,24 +1003,10 @@

Source: components/plot.js

// Backwards compatible check: there was previously a third option. Anything truthy should thus act as "responsive_resize: true" this.layout.responsive_resize = !!this.layout.responsive_resize; - // If this is a responsive layout then set a namespaced/unique onresize event listener on the window - if (this.layout.responsive_resize) { - const resize_listener = () => this.rescaleSVG(); - window.addEventListener('resize', resize_listener); - this.trackExternalListener(window, 'resize', resize_listener); - - // Forcing one additional setDimensions() call after the page is loaded clears up - // any disagreements between the initial layout and the loaded responsive container's size - const load_listener = () => this.setDimensions(); - window.addEventListener('load', load_listener); - this.trackExternalListener(window, 'load', load_listener); - } - // Add panels this.layout.panels.forEach((panel_layout) => { this.addPanel(panel_layout); }); - return this; } @@ -983,7 +1039,7 @@

Source: components/plot.js

} // Resize/reposition panels to fit, update proportional origins if necessary let y_offset = 0; - this.panel_ids_by_y_index.forEach((panel_id) => { + this._panel_ids_by_y_index.forEach((panel_id) => { const panel = this.panels[panel_id]; const panel_width = this.layout.width; // In this block, we are passing explicit dimensions that might require rescaling all panels at once @@ -1009,8 +1065,8 @@

Source: components/plot.js

} // If the plot has been initialized then trigger some necessary render functions - if (this.initialized) { - this.panel_boundaries.position(); + if (this._initialized) { + this._panel_boundaries.position(); this.toolbar.update(); this.curtain.update(); this.loader.update(); @@ -1035,27 +1091,28 @@

Source: components/plot.js

// Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate // proportional heights for all panels with a null value from discretely set dimensions. // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width) - for (let id in this.panels) { - if (this.panels[id].layout.interaction.x_linked) { - x_linked_margins.left = Math.max(x_linked_margins.left, this.panels[id].layout.margin.left); - x_linked_margins.right = Math.max(x_linked_margins.right, this.panels[id].layout.margin.right); + for (let panel of Object.values(this.panels)) { + if (panel.layout.interaction.x_linked) { + x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left); + x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right); } } // Update origins on all panels without changing plot-level dimensions yet // Also apply x-linked margins to x-linked panels, updating widths as needed let y_offset = 0; - this.panel_ids_by_y_index.forEach((panel_id) => { + this._panel_ids_by_y_index.forEach((panel_id) => { const panel = this.panels[panel_id]; + const panel_layout = panel.layout; panel.setOrigin(0, y_offset); y_offset += this.panels[panel_id].layout.height; - if (panel.layout.interaction.x_linked) { - const delta = Math.max(x_linked_margins.left - panel.layout.margin.left, 0) - + Math.max(x_linked_margins.right - panel.layout.margin.right, 0); - panel.layout.width += delta; - panel.layout.margin.left = x_linked_margins.left; - panel.layout.margin.right = x_linked_margins.right; - panel.layout.cliparea.origin.x = x_linked_margins.left; + if (panel_layout.interaction.x_linked) { + const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0) + + Math.max(x_linked_margins.right - panel_layout.margin.right, 0); + panel_layout.width += delta; + panel_layout.margin.left = x_linked_margins.left; + panel_layout.margin.right = x_linked_margins.right; + panel_layout.cliparea.origin.x = x_linked_margins.left; } }); @@ -1064,7 +1121,7 @@

Source: components/plot.js

this.setDimensions(); // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions - this.panel_ids_by_y_index.forEach((panel_id) => { + this._panel_ids_by_y_index.forEach((panel_id) => { const panel = this.panels[panel_id]; panel.setDimensions( this.layout.width, @@ -1082,10 +1139,33 @@

Source: components/plot.js

* @returns {Plot} */ initialize() { - // Ensure proper responsive class is present on the containing node if called for if (this.layout.responsive_resize) { d3.select(this.container).classed('lz-container-responsive', true); + + // If this is a responsive layout then set a namespaced/unique onresize event listener on the window + const resize_listener = () => this.rescaleSVG(); + window.addEventListener('resize', resize_listener); + this.trackExternalListener(window, 'resize', resize_listener); + + // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener + // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed + if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers + const options = { root: document.documentElement, threshold: 0.9 }; + const observer = new IntersectionObserver((entries, observer) => { + if (entries.some((entry) => entry.intersectionRatio > 0)) { + this.rescaleSVG(); + } + }, options); + // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup + observer.observe(this.container); + } + + // Forcing one additional setDimensions() call after the page is loaded clears up + // any disagreements between the initial layout and the loaded responsive container's size + const load_listener = () => this.setDimensions(); + window.addEventListener('load', load_listener); + this.trackExternalListener(window, 'load', load_listener); } // Create an element/layer for containing mouse guides @@ -1099,7 +1179,7 @@

Source: components/plot.js

const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect') .attr('class', 'lz-mouse_guide-horizontal') .attr('y', -1); - this.mouse_guide = { + this._mouse_guide = { svg: mouse_guide_svg, vertical: mouse_guide_vertical_svg, horizontal: mouse_guide_horizontal_svg, @@ -1111,7 +1191,7 @@

Source: components/plot.js

this.loader = generateLoader.call(this); // Create the panel_boundaries object with show/position/hide methods - this.panel_boundaries = { + this._panel_boundaries = { parent: this, hide_timeout: null, showing: false, @@ -1123,7 +1203,7 @@

Source: components/plot.js

if (!this.showing && !this.parent.curtain.showing) { this.showing = true; // Loop through all panels to create a horizontal boundary for each - this.parent.panel_ids_by_y_index.forEach((panel_id, panel_idx) => { + this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => { const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip') .attr('class', 'lz-panel-boundary') .attr('title', 'Resize panel'); @@ -1137,15 +1217,15 @@

Source: components/plot.js

}); panel_resize_drag.on('drag', () => { // First set the dimensions on the panel we're resizing - const this_panel = this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]]; + const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]]; const original_panel_height = this_panel.layout.height; this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy); const panel_height_change = this_panel.layout.height - original_panel_height; // Next loop through all panels. // Update proportional dimensions for all panels including the one we've resized using discrete heights. // Reposition panels with a greater y-index than this panel to their appropriate new origin. - this.parent.panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => { - const loop_panel = this.parent.panels[this.parent.panel_ids_by_y_index[loop_panel_idx]]; + this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => { + const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]]; if (loop_panel_idx > panel_idx) { loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change); loop_panel.toolbar.position(); @@ -1156,7 +1236,7 @@

Source: components/plot.js

this.position(); }); selector.call(panel_resize_drag); - this.parent.panel_boundaries.selectors.push(selector); + this.parent._panel_boundaries.selectors.push(selector); }); // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot const corner_selector = d3.select(this.parent.svg.node().parentNode) @@ -1182,7 +1262,7 @@

Source: components/plot.js

this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy); }); corner_selector.call(corner_drag); - this.parent.panel_boundaries.corner_selector = corner_selector; + this.parent._panel_boundaries.corner_selector = corner_selector; } return this.position(); }, @@ -1193,7 +1273,7 @@

Source: components/plot.js

// Position panel boundaries const plot_page_origin = this.parent._getPageOrigin(); this.selectors.forEach((selector, panel_idx) => { - const panel = this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]]; + const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]]; const panel_page_origin = panel._getPageOrigin(); const left = plot_page_origin.x; const top = panel_page_origin.y + panel.layout.height - 12; @@ -1234,12 +1314,12 @@

Source: components/plot.js

if (this.layout.panel_boundaries) { d3.select(this.svg.node().parentNode) .on(`mouseover.${this.id}.panel_boundaries`, () => { - clearTimeout(this.panel_boundaries.hide_timeout); - this.panel_boundaries.show(); + clearTimeout(this._panel_boundaries.hide_timeout); + this._panel_boundaries.show(); }) .on(`mouseout.${this.id}.panel_boundaries`, () => { - this.panel_boundaries.hide_timeout = setTimeout(() => { - this.panel_boundaries.hide(); + this._panel_boundaries.hide_timeout = setTimeout(() => { + this._panel_boundaries.hide(); }, 300); }); } @@ -1256,13 +1336,13 @@

Source: components/plot.js

const namespace = `.${this.id}`; if (this.layout.mouse_guide) { const mouseout_mouse_guide = () => { - this.mouse_guide.vertical.attr('x', -1); - this.mouse_guide.horizontal.attr('y', -1); + this._mouse_guide.vertical.attr('x', -1); + this._mouse_guide.horizontal.attr('y', -1); }; const mousemove_mouse_guide = () => { const coords = d3.mouse(this.svg.node()); - this.mouse_guide.vertical.attr('x', coords[0]); - this.mouse_guide.horizontal.attr('y', coords[1]); + this._mouse_guide.vertical.attr('x', coords[0]); + this._mouse_guide.horizontal.attr('y', coords[1]); }; this.svg .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide) @@ -1273,15 +1353,16 @@

Source: components/plot.js

this.stopDrag(); }; const mousemove = () => { - if (this.interaction.dragging) { + const { _interaction } = this; + if (_interaction.dragging) { const coords = d3.mouse(this.svg.node()); if (d3.event) { d3.event.preventDefault(); } - this.interaction.dragging.dragged_x = coords[0] - this.interaction.dragging.start_x; - this.interaction.dragging.dragged_y = coords[1] - this.interaction.dragging.start_y; - this.panels[this.interaction.panel_id].render(); - this.interaction.linked_panel_ids.forEach((panel_id) => { + _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x; + _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y; + this.panels[_interaction.panel_id].render(); + _interaction.linked_panel_ids.forEach((panel_id) => { this.panels[panel_id].render(); }); } @@ -1323,7 +1404,7 @@

Source: components/plot.js

this.applyState({ lz_match_value: to_send }); }); - this.initialized = true; + this._initialized = true; // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip // positioning. TODO: make this additional call unnecessary. @@ -1365,7 +1446,7 @@

Source: components/plot.js

} const coords = d3.mouse(this.svg.node()); - this.interaction = { + this._interaction = { panel_id: panel.id, linked_panel_ids: panel.getLinkedPanelIds(axis), dragging: { @@ -1390,22 +1471,22 @@

Source: components/plot.js

* @returns {Plot} */ stopDrag() { - - if (!this.interaction.dragging) { + const { _interaction } = this; + if (!_interaction.dragging) { return this; } - if (typeof this.panels[this.interaction.panel_id] != 'object') { - this.interaction = {}; + if (typeof this.panels[_interaction.panel_id] != 'object') { + this._interaction = {}; return this; } - const panel = this.panels[this.interaction.panel_id]; + const panel = this.panels[_interaction.panel_id]; // Helper function to find the appropriate axis layouts on child data layers // Once found, apply the extent as floor/ceiling and remove all other directives // This forces all associated axes to conform to the extent generated by a drag action const overrideAxisLayout = (axis, axis_number, extent) => { - panel.data_layer_ids_by_z_index.forEach((id) => { + panel._data_layer_ids_by_z_index.forEach((id) => { const axis_layout = panel.data_layers[id].layout[`${axis}_axis`]; if (axis_layout.axis === axis_number) { axis_layout.floor = extent[0]; @@ -1418,24 +1499,24 @@

Source: components/plot.js

}); }; - switch (this.interaction.dragging.method) { + switch (_interaction.dragging.method) { case 'background': case 'x_tick': - if (this.interaction.dragging.dragged_x !== 0) { + if (_interaction.dragging.dragged_x !== 0) { overrideAxisLayout('x', 1, panel.x_extent); this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] }); } break; case 'y1_tick': case 'y2_tick': - if (this.interaction.dragging.dragged_y !== 0) { - const y_axis_number = parseInt(this.interaction.dragging.method[1]); + if (_interaction.dragging.dragged_y !== 0) { + const y_axis_number = parseInt(_interaction.dragging.method[1]); overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]); } break; } - this.interaction = {}; + this._interaction = {}; this.svg.style('cursor', null); return this; @@ -1462,7 +1543,7 @@

Source: components/plot.js


diff --git a/docs/api/components_toolbar_index.js.html b/docs/api/components_toolbar_index.js.html index d6f2712e..a75bbe08 100644 --- a/docs/api/components_toolbar_index.js.html +++ b/docs/api/components_toolbar_index.js.html @@ -26,7 +26,7 @@

Source: components/toolbar/index.js

-
import widgets from '../../registry/widgets';
+            
import WIDGETS from '../../registry/widgets';
 import * as d3 from 'd3';
 
 /**
@@ -88,21 +88,14 @@ 

Source: components/toolbar/index.js

*/ initialize() { // Parse layout to generate widget instances - // In LZ 0.12, `dashboard.components` was renamed to `toolbar.widgets`. Preserve a backwards-compatible alias. - const options = (this.parent.layout.dashboard && this.parent.layout.dashboard.components) || this.parent.layout.toolbar.widgets; + const options = this.parent.layout.toolbar.widgets; if (Array.isArray(options)) { options.forEach((layout) => { - try { - const widget = widgets.create(layout.type, layout, this); - this.widgets.push(widget); - } catch (e) { - console.warn('Failed to create widget'); - console.error(e); - } + this.addWidget(layout); }); } - // Add mouseover event handlers to show/hide panel toolbar + // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown) if (this.type === 'panel') { d3.select(this.parent.parent.svg.node().parentNode) .on(`mouseover.${this.id}`, () => { @@ -121,6 +114,26 @@

Source: components/toolbar/index.js

return this; } + /** + * Add a new widget to the toolbar. + * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to: + * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here. + * - call widget.show() to ensure that the widget is initialized and rendered correctly + * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default. + * @param {Object} layout The layout object describing the desired widget + * @returns {layout.type} + */ + addWidget(layout) { + try { + const widget = WIDGETS.create(layout.type, layout, this); + this.widgets.push(widget); + return widget; + } catch (e) { + console.warn('Failed to create widget'); + console.error(e); + } + } + /** * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged * in an active drag event. @@ -136,7 +149,7 @@

Source: components/toolbar/index.js

persist = persist || widget.shouldPersist(); }); // Persist if in a parent drag event - persist = persist || (this.parent_plot.panel_boundaries.dragging || this.parent_plot.interaction.dragging); + persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging); return !!persist; } @@ -258,7 +271,7 @@

Source: components/toolbar/index.js


diff --git a/docs/api/components_toolbar_widgets.js.html b/docs/api/components_toolbar_widgets.js.html index 9e222988..d419bbcf 100644 --- a/docs/api/components_toolbar_widgets.js.html +++ b/docs/api/components_toolbar_widgets.js.html @@ -1170,7 +1170,7 @@

Source: components/toolbar/widgets.js

class MovePanelDown extends BaseWidget { update () { if (this.button) { - const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot.panel_ids_by_y_index.length - 1); + const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1); this.button.disable(is_at_bottom); return this; } @@ -1671,7 +1671,7 @@

Source: components/toolbar/widgets.js


diff --git a/docs/api/data_adapters.js.html b/docs/api/data_adapters.js.html index 487ea043..afe9fd17 100644 --- a/docs/api/data_adapters.js.html +++ b/docs/api/data_adapters.js.html @@ -50,8 +50,8 @@

Source: data/adapters.js

* * ``` * const data_sources = new LocusZoom.DataSources(); - * data_sources.add("trait1", ["AssociationLZ", {url: "http://server.com/api/single/", params: {source: 1}}]); - * data_sources.add("trait2", ["AssociationLZ", {url: "http://server.com/api/single/", params: {source: 2}}]); + * data_sources.add("trait1", ["AssociationLZ", { url: "http://server.com/api/single/", source: 1 }]); + * data_sources.add("trait2", ["AssociationLZ", { url: "http://server.com/api/single/", source: 2 }]); * ``` * * These data sources are then passed to the plot when data is to be rendered: @@ -60,170 +60,187 @@

Source: data/adapters.js

* @module LocusZoom_Adapters */ -function validateBuildSource(class_name, build, source) { - // Build OR Source, not both - if ((build && source) || !(build || source)) { - throw new Error(`${class_name} must provide a parameter specifying either "build" or "source". It should not specify both.`); - } - // If the build isn't recognized, our APIs can't transparently select a source to match - if (build && !['GRCh37', 'GRCh38'].includes(build)) { - throw new Error(`${class_name} must specify a valid genome build number`); - } -} +import {BaseUrlAdapter} from 'undercomplicate'; +import {parseMarker} from '../helpers/parse'; -// NOTE: Custom adapaters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs. +// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs. // Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal // methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the // private API methods exist in the base class. /** - * Base class for LocusZoom data sources (any). See also: BaseApiAdapter for requests from a remote URL. + * Replaced with the BaseLZAdapter class. * @public + * @deprecated */ class BaseAdapter { - /** - * @param {object} config Configuration options - */ - constructor(config) { - /** - * Whether to enable caching (true for most data adapters) - * @private - * @member {Boolean} - */ - this._enableCache = true; - this._cachedKey = null; - - // Almost all LZ sources are "region based". Cache the region requested and use it to determine whether - // the cache would satisfy the request. - this._cache_pos_start = null; - this._cache_pos_end = null; - - /** - * Whether this adapter type is dependent on previous requests- for example, the LD source cannot annotate - * association data if no data was found for that region. - * @private - * @member {boolean} - */ - this.__dependentSource = false; - - // Parse configuration options - this.parseInit(config); + constructor() { + throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.'); } +} - /** - * Parse configuration used to create the instance. Many custom sources will override this method to suit their - * needs (eg specific config options, or for sources that do not retrieve data from a URL) - * @protected - * @param {String|Object} config Basic configuration- either a url, or a config object - * @param {String} [config.url] The datasource URL - * @param {String} [config.params] Initial config params for the datasource - */ - parseInit(config) { - /** - * @private - * @member {Object} - */ - this.params = config.params || {}; +/** + * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters + * (and potentially other behavior) that are relevant to URL-based requests. + * @extends module:LocusZoom_Adapters~BaseAdapter + * @deprecated + * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request. + * @inheritDoc + */ +class BaseApiAdapter extends BaseAdapter {} + + +/** + * @param {object} config + * @param [config.cache_enabled=true] + * @param [config.cache_size=3] + * @param [config.url] + * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name. + * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant) + * Typically, this is only disabled if the response payload is very unusual + * @param {String[]} [limit_fields=null] If an API returns far more data than is needed, this can be used to simplify + * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD. + */ +class BaseLZAdapter extends BaseUrlAdapter { + constructor(config = {}) { + if (config.params) { + // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places. + console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'); + Object.assign(config, config.params || {}); + delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality! + } + super(config); + + // Prefix the namespace for this source to all fieldnames: id -> assoc.id + // This is useful for almost all layers because the layout object says where to find every field, exactly. + // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on + // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear + // in the response. (gene_name instead of genes.gene_name) + const { prefix_namespace = true, limit_fields } = config; + this._prefix_namespace = prefix_namespace; + this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields } /** - * A unique identifier that indicates whether cached data is valid for this request. For most sources using GET - * requests to a REST API, this is usually the region requested. Some sources will append additional params to define the request. - * - * This means that to change caching behavior, both the URL and the cache key may need to be updated. However, - * it allows most datasources to skip an extra network request when zooming in. - * @protected - * @param {Object} state Information available in plot.state (chr, start, end). Sometimes used to inject globally - * available information that influences the request being made. - * @param {Object} chain The data chain from previous requests made in a sequence. - * @param fields - * @returns {String} + * Determine how a particular request will be identified in cache. Most LZ requests are region based, + * so the default is a string concatenation of `chr_start_end` + * @param options Receives plot.state plus any other request options defined by this source + * @returns {string} + * @private */ - getCacheKey(state, chain, fields) { - // Most region sources, by default, will cache the largest region that satisfies the request: zooming in - // should be satisfied via the cache, but pan or region change operations will cause a network request - - // Some adapters rely on values set in chain.header during the getURL call. (eg, the LD source uses - // this to find the LD refvar) Calling this method is a backwards-compatible way of ensuring that value is set, - // even on a cache hit in which getURL otherwise wouldn't be called. - // Some of the adapters that rely on this behavior are user-defined, hence compatibility hack - this.getURL(state, chain, fields); - - const cache_pos_chr = state.chr; - const {_cache_pos_start, _cache_pos_end} = this; - if (_cache_pos_start && state.start >= _cache_pos_start && _cache_pos_end && state.end <= _cache_pos_end ) { - return `${cache_pos_chr}_${_cache_pos_start}_${_cache_pos_end}`; - } else { - return `${state.chr}_${state.start}_${state.end}`; + _getCacheKey(options) { + // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default + let {chr, start, end} = options; // Current view: plot.state + + // Does a prior cache hit qualify as a superset of the ROI? + const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end); + if (superset) { + ({ chr, start, end } = superset.metadata); } + + // The default cache key is region-based, and this method only returns the region-based part of the cache hit + // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check + options._cache_meta = { chr, start, end }; + return `${chr}_${start}_${end}`; } /** - * Stub: build the URL for any requests made by this source. - * @protected + * Add the "local namespace" as a prefix for every field returned for this request. Eg if the association api + * returns a field called variant, and the source is referred to as "assoc" within a particular data layer, then + * the returned records will have a field called "assoc:variant" + * + * @param records + * @param options + * @returns {*} + * @private */ - getURL(state, chain, fields) { - return this.url; + _postProcessResponse(records, options) { + if (!this._prefix_namespace || !Array.isArray(records)) { + return records; + } + + // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for + // assoc data might see "variant" as "assoc.variant" + const { _limit_fields } = this; + const { _provider_name } = options; + + return records.map((row) => { + return Object.entries(row).reduce( + (acc, [label, value]) => { + // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload. + if (!_limit_fields || _limit_fields.has(label)) { + acc[`${_provider_name}:${label}`] = value; + } + return acc; + }, + {} + ); + }); } /** - * Perform a network request to fetch data for this source. This is usually the method that is used to override - * when defining how to retrieve data. - * @protected - * @param {Object} state The state of the parent plot - * @param chain - * @param fields - * @returns {Promise} + * Convenience method, manually called in LZ sources that deal with dependent data. + * + * In the last step of fetching data, LZ adds a prefix to each field name. + * This means that operations like "build query based on prior data" can't just ask for "log_pvalue" because + * they are receiving "assoc:log_pvalue" or some such unknown prefix. + * + * This helper lets us use dependent data more easily. Not every adapter needs to use this method. + * + * @param {Object} a_record One record (often the first one in a set of records) + * @param {String} fieldname The desired fieldname, eg "log_pvalue" */ - fetchRequest(state, chain, fields) { - const url = this.getURL(state, chain, fields); - return fetch(url).then((response) => { - if (!response.ok) { - throw new Error(response.statusText); - } - return response.text(); - }); + _findPrefixedKey(a_record, fieldname) { + const suffixer = new RegExp(`:${fieldname}$`); + const match = Object.keys(a_record).find((key) => suffixer.test(key)); + if (!match) { + throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`); + } + return match; } +} + +/** + * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies + * of one particular web server. + */ +class BaseUMAdapter extends BaseLZAdapter { /** - * Gets the data for just this source, typically via a network request (but using cache where possible) - * - * For most use cases, it is better to override `fetchRequest` instead, to avoid bypassing the cache mechanism - * by accident. - * @protected - * @return {Promise} + * @param {Object} config + * @param {String} [config.build] The genome build to be used by all requests for this adapter. */ - getRequest(state, chain, fields) { - let req; - const cacheKey = this.getCacheKey(state, chain, fields); + constructor(config = {}) { + super(config); + // The UM portaldev API accepts an (optional) parameter "genome_build" + this._genome_build = config.genome_build || config.build; + } - if (this._enableCache && typeof(cacheKey) !== 'undefined' && cacheKey === this._cachedKey) { - req = Promise.resolve(this._cachedResponse); // Resolve to the value of the current promise - } else { - req = this.fetchRequest(state, chain, fields); - if (this._enableCache) { - this._cachedKey = cacheKey; - this._cache_pos_start = state.start; - this._cache_pos_end = state.end; - this._cachedResponse = req; - } + _validateBuildSource(build, source) { + // Build OR Source, not both + if ((build && source) || !(build || source)) { + throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`); + } + // If the build isn't recognized, our APIs can't transparently select a source to match + if (build && !['GRCh37', 'GRCh38'].includes(build)) { + throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`); } - return req; } + // Special behavior for the UM portaldev API: col -> row format normalization /** - * Ensure the server response is in a canonical form, an array of one object per record. [ {field: oneval} ]. - * If the server response contains columns, reformats the response from {column1: [], column2: []} to the above. - * - * Does not apply namespacing, transformations, or field extraction. - * - * May be overridden by data adapters that inherently return more complex payloads, or that exist to annotate other - * sources (eg, if the payload provides extra data rather than a series of records). - * @protected - * @param {Object[]|Object} data The original parsed server response + * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs. + * @param response_text + * @param options + * @returns {Object[]} + * @private */ - normalizeResponse(data) { + _normalizeResponse(response_text, options) { + let data = super._normalizeResponse(...arguments); + // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload + data = data.data || data; + if (Array.isArray(data)) { // Already in the desired form return data; @@ -240,7 +257,7 @@

Source: data/adapters.js

throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`); } - // Go down the rows, and create an object for each record + // Go down the columns, and create an object for each row record const records = []; const fields = Object.keys(data); for (let i = 0; i < N; i++) { @@ -252,568 +269,34 @@

Source: data/adapters.js

} return records; } - - /** - * Hook to post-process the data returned by this source with new, additional behavior. - * (eg cleaning up API values or performing complex calculations on the returned data) - * - * @protected - * @param {Object[]} records The parsed data from the source (eg standardized api response) - * @param {Object} chain The data chain object. For example, chain.headers may provide useful annotation metadata - * @returns {Object[]|Promise} The modified set of records - */ - annotateData(records, chain) { - // Default behavior: no transformations - return records; - } - - /** - * Clean up the server records for use by datalayers: extract only certain fields, with the specified names. - * Apply per-field transformations as appropriate. - * - * This hook can be overridden, eg to create a source that always returns all records and ignores the "fields" array. - * This is particularly common for sources at the end of a chain- many "dependent" sources do not allow - * cherry-picking individual fields, in which case by **convention** the fields array specifies "last_source_name:all" - * - * @protected - * @param {Object[]} data One record object per element - * @param {String[]} fields The names of fields to extract (as named in the source data). Eg "afield" - * @param {String[]} outnames How to represent the source fields in the output. Eg "namespace:afield|atransform" - * @param {function[]} trans An array of transformation functions (if any). One function per data element, or null. - * @protected - */ - extractFields (data, fields, outnames, trans) { - //intended for an array of objects - // [ {"id":1, "val":5}, {"id":2, "val":10}] - // Since a number of sources exist that do not obey this format, we will provide a convenient pass-through - if (!Array.isArray(data)) { - return data; - } - - if (!data.length) { - // Sometimes there are regions that just don't have data- this should not trigger a missing field error message! - return data; - } - - const fieldFound = []; - for (let k = 0; k < fields.length; k++) { - fieldFound[k] = 0; - } - - const records = data.map(function (item) { - const output_record = {}; - for (let j = 0; j < fields.length; j++) { - let val = item[fields[j]]; - if (typeof val != 'undefined') { - fieldFound[j] = 1; - } - if (trans && trans[j]) { - val = trans[j](val); - } - output_record[outnames[j]] = val; - } - return output_record; - }); - fieldFound.forEach(function(v, i) { - if (!v) { - throw new Error(`field ${fields[i]} not found in response for ${outnames[i]}`); - } - }); - return records; - } - - /** - * Combine records from this source with others in the chain to yield final chain body. - * Handles merging this data with other sources (if applicable). - * - * @protected - * @param {Object[]} data The data That would be returned from this source alone - * @param {Object} chain The data chain built up during previous requests - * @param {String[]} fields - * @param {String[]} outnames - * @param {String[]} trans - * @return {Promise|Object[]} The new chain body - */ - combineChainBody(data, chain, fields, outnames, trans) { - return data; - } - - /** - * Coordinates the work of parsing a response and returning records. This is broken into 4 steps, which may be - * overridden separately for fine-grained control. Each step can return either raw data or a promise. - * - * @see {module:LocusZoom_Adapters~BaseAdapter#normalizeResponse} - * @see {module:LocusZoom_Adapters~BaseAdapter#annotateData} - * @see {module:LocusZoom_Adapters~BaseAdapter#extractFields} - * @see {module:LocusZoom_Adapters~BaseAdapter#combineChainBody} - * @protected - * - * @param {String|Object} resp The raw data associated with the response - * @param {Object} chain The combined parsed response data from this and all other requests made in the chain - * @param {String[]} fields Array of requested field names (as they would appear in the response payload) - * @param {String[]} outnames Array of field names as they will be represented in the data returned by this source, - * including the namespace. This must be an array with the same length as `fields` - * @param {Function[]} trans The collection of transformation functions to be run on selected fields. - * This must be an array with the same length as `fields` - * @returns {Promise} A promise that resolves to an object containing - * request metadata (`headers: {}`), the consolidated data for plotting (`body: []`), and the individual responses that would be - * returned by each source in the chain in isolation (`discrete: {}`) - */ - parseResponse (resp, chain, fields, outnames, trans) { - const source_id = this.source_id || this.constructor.name; - if (!chain.discrete) { - chain.discrete = {}; - } - - const json = typeof resp == 'string' ? JSON.parse(resp) : resp; - - // Perform the 4 steps of parsing the payload and return a combined chain object - return Promise.resolve(this.normalizeResponse(json.data || json)) - .then((standardized) => { - // Perform calculations on the data from just this source - return Promise.resolve(this.annotateData(standardized, chain)); - }).then((data) => { - return Promise.resolve(this.extractFields(data, fields, outnames, trans)); - }).then((one_source_body) => { - // Store a copy of the data that would be returned by parsing this source in isolation (and taking the - // fields array into account). This is useful when we want to re-use the source output in many ways. - chain.discrete[source_id] = one_source_body; - return Promise.resolve(this.combineChainBody(one_source_body, chain, fields, outnames, trans)); - }).then((new_body) => { - return { header: chain.header || {}, discrete: chain.discrete, body: new_body }; - }); - } - - /** - * Fetch the data from the specified data adapter, and apply transformations requested by an external consumer. - * This is the public-facing datasource method that will most be called by the plot, but custom data adapters will - * almost never want to override this method directly- more specific hooks are provided to control individual pieces - * of the request lifecycle. - * - * @private - * @param {Object} state The current "state" of the plot, such as chromosome and start/end positions - * @param {String[]} fields Array of field names that the plot has requested from this data adapter. (without the "namespace" prefix) - * @param {String[]} outnames Array describing how the output data should refer to this field. This represents the - * originally requested field name, including the namespace. This must be an array with the same length as `fields` - * @param {Function[]} trans The collection of transformation functions to be run on selected fields. - * This must be an array with the same length as `fields` - * @returns {function} A callable operation that can be used as part of the data chain - */ - getData(state, fields, outnames, trans) { - if (this.preGetData) { // TODO try to remove this method if at all possible - const pre = this.preGetData(state, fields, outnames, trans); - if (this.pre) { - state = pre.state || state; - fields = pre.fields || fields; - outnames = pre.outnames || outnames; - trans = pre.trans || trans; - } - } - - return (chain) => { - if (this.__dependentSource && chain && chain.body && !chain.body.length) { - // A "dependent" source should not attempt to fire a request if there is no data for it to act on. - // Therefore, it should simply return the previous data chain. - return Promise.resolve(chain); - } - - return this.getRequest(state, chain, fields).then((resp) => { - return this.parseResponse(resp, chain, fields, outnames, trans); - }); - }; - } } -/** - * Base class for LocusZoom data adapters that receive their data over the web. Adds default config parameters - * (and potentially other behavior) that are relevant to URL-based requests. - * @extends module:LocusZoom_Adapters~BaseAdapter - * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request. - * @inheritDoc - */ -class BaseApiAdapter extends BaseAdapter { - parseInit(config) { - super.parseInit(config); - - /** - * @private - * @member {String} - */ - this.url = config.url; - if (!this.url) { - throw new Error('Source not initialized with required URL'); - } - } -} /** * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request * to a specific REST API. * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter - * @param {string} config.url The base URL for the remote data. - * @param {object} config.params - * @param [config.params.sort=false] Whether to sort the association data (by an assumed field named "position"). This - * is primarily a site-specific workaround for a particular LZ usage; we encourage apis to sort by position before returning - * data to the browser. - * @param [config.params.source] The ID of the GWAS dataset to use for this request, as matching the API backend - */ -class AssociationLZ extends BaseApiAdapter { - preGetData (state, fields, outnames, trans) { - // TODO: Modify internals to see if we can go without this method - const id_field = this.params.id_field || 'id'; - [id_field, 'position'].forEach(function(x) { - if (!fields.includes(x)) { - fields.unshift(x); - outnames.unshift(x); - trans.unshift(null); - } - }); - return {fields: fields, outnames:outnames, trans:trans}; - } - - /** - * Add query parameters to the URL to construct a query for the specified region - */ - getURL (state, chain, fields) { - const analysis = chain.header.analysis || this.params.source || this.params.analysis; // Old usages called this param "analysis" - if (typeof analysis == 'undefined') { - throw new Error('Association source must specify an analysis ID to plot'); - } - return `${this.url}results/?filter=analysis in ${analysis} and chromosome in '${state.chr}' and position ge ${state.start} and position le ${state.end}`; - } - - /** - * Some association sources do not sort their data in a predictable order, which makes it hard to reliably - * align with other sources (such as LD). For performance reasons, sorting is an opt-in argument. - * TODO: Consider more fine grained sorting control in the future. This was added as a very specific - * workaround for the original T2D portal. - * @protected - * @param data - * @return {Object} - */ - normalizeResponse (data) { - data = super.normalizeResponse(data); - if (this.params && this.params.sort && data.length && data[0]['position']) { - data.sort(function (a, b) { - return a['position'] - b['position']; - }); - } - return data; - } -} - -/** - * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant. - * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS - * variant (smallest pvalue or largest neg_log_pvalue) and yse that as the LD reference variant. - * - * This source is designed to connect its results to association data, and therefore depends on association data having - * been loaded by a previous request in the data chain. For custom association APIs, some additional options might - * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt` - * are preferred, but this source will attempt to harmonize other common data formats into something that the LD - * server can understand. + * @see module:LocusZoom_Adapters~BaseUMAdapter * - * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL */ -class LDServer extends BaseApiAdapter { - /** - * @param {string} config.url The base URL for the remote data. - * @param {object} config.params - * @param [config.params.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant. - * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. - * @param [config.params.source='1000G'] The name of the reference panel to use, as specified in the LD server instance. - * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display. - * @param [config.params.population='ALL'] The sample population used to calculate LD for a specified source; - * population names vary depending on the reference panel and how the server was populated wth data. - * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display. - * @param [config.params.method='rsquare'] The metric used to calculate LD - * @param [config.params.id_field] The association data field that contains variant identifier information. The preferred - * format of LD server is `chrom:pos_ref/alt` and this source will attempt to normalize other common formats. - * This source can auto-detect possible matches for field names containing "variant" or "id" - * @param [config.params.position_field] The association data field that contains variant position information. - * This source can auto-detect possible matches for field names containing "position" or "pos" - * @param [config.params.pvalue_field] The association data field that contains pvalue information. - * This source can auto-detect possible matches for field names containing "pvalue" or "log_pvalue". - * The suggested LD refvar will be the smallest pvalue, or the largest log_pvalue: this source will auto-detect - * the word "log" in order to determine the sign of the comparison. - */ - constructor(config) { +class AssociationLZ extends BaseUMAdapter { + constructor(config = {}) { super(config); - this.__dependentSource = true; - } - - preGetData(state, fields) { - if (fields.length > 1) { - if (fields.length !== 2 || !fields.includes('isrefvar')) { - throw new Error(`LD does not know how to get all fields: ${fields.join(', ')}`); - } - } - } - - findMergeFields(chain) { - // Find the fields (as provided by a previous step in the chain, like an association source) that will be needed to - // combine LD data with existing information - - // Since LD information may be shared across multiple assoc sources with different namespaces, - // we use regex to find columns to join on, rather than requiring exact matches - const exactMatch = function (arr) { - return function () { - const regexes = arguments; - for (let i = 0; i < regexes.length; i++) { - const regex = regexes[i]; - const m = arr.filter(function (x) { - return x.match(regex); - }); - if (m.length) { - return m[0]; - } - } - return null; - }; - }; - let dataFields = { - id: this.params.id_field, - position: this.params.position_field, - pvalue: this.params.pvalue_field, - _names_:null, - }; - if (chain && chain.body && chain.body.length > 0) { - const names = Object.keys(chain.body[0]); - const nameMatch = exactMatch(names); - // Internally, fields are generally prefixed with the name of the source they come from. - // If the user provides an id_field (like `variant`), it should work across data sources (`assoc1:variant`, - // assoc2:variant), but not match fragments of other field names (assoc1:variant_thing) - // Note: these lookups hard-code a couple of common fields that will work based on known APIs in the wild - const id_match = dataFields.id && nameMatch(new RegExp(`${dataFields.id}\\b`)); - dataFields.id = id_match || nameMatch(/\bvariant\b/) || nameMatch(/\bid\b/); - dataFields.position = dataFields.position || nameMatch(/\bposition\b/i, /\bpos\b/i); - dataFields.pvalue = dataFields.pvalue || nameMatch(/\bpvalue\b/i, /\blog_pvalue\b/i); - dataFields._names_ = names; - } - return dataFields; - } - findRequestedFields (fields, outnames) { - // Assumption: all usages of this source will only ever ask for "isrefvar" or "state". This maps to output names. - let obj = {}; - for (let i = 0; i < fields.length; i++) { - if (fields[i] === 'isrefvar') { - obj.isrefvarin = fields[i]; - obj.isrefvarout = outnames && outnames[i]; - } else { - obj.ldin = fields[i]; - obj.ldout = outnames && outnames[i]; - } - } - return obj; + // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files + const { source } = config; + this._source_id = source; } - /** - * The LD API payload does not obey standard format conventions; do not try to transform it. - */ - normalizeResponse (data) { - return data; - } - - /** - * Get the LD reference variant, which by default will be the most significant hit in the assoc results - * This will be used in making the original query to the LD server for pairwise LD information. - * - * This is meant to join a single LD request to any number of association results, and to work with many kinds of API. - * To do this, the datasource looks for fields with special known names such as pvalue, log_pvalue, etc. - * If your API uses different nomenclature, an option must be specified. - * - * @protected - * @returns {String[]} Two strings: 1) the marker id (expected to be in `chr:pos_ref/alt` format) of the reference - * variant, and 2) the marker ID as it appears in the original dataset that we are joining to, so that the exact - * refvar can be marked when plotting the data.. - */ - getRefvar(state, chain, fields) { - let findExtremeValue = function(records, pval_field) { - // Finds the most significant hit (smallest pvalue, or largest -log10p). Will try to auto-detect the appropriate comparison. - pval_field = pval_field || 'log_pvalue'; // The official LZ API returns log_pvalue - const is_log = /log/.test(pval_field); - let cmp; - if (is_log) { - cmp = function(a, b) { - return a > b; - }; - } else { - cmp = function(a, b) { - return a < b; - }; - } - let extremeVal = records[0][pval_field], extremeIdx = 0; - for (let i = 1; i < records.length; i++) { - if (cmp(records[i][pval_field], extremeVal)) { - extremeVal = records[i][pval_field]; - extremeIdx = i; - } - } - return extremeIdx; - }; - - let reqFields = this.findRequestedFields(fields); - let refVar = reqFields.ldin; - if (refVar === 'state') { - refVar = state.ldrefvar || chain.header.ldrefvar || 'best'; - } - if (refVar === 'best') { - if (!chain.body) { - throw new Error('No association data found to find best pvalue'); - } - let keys = this.findMergeFields(chain); - if (!keys.pvalue || !keys.id) { - let columns = ''; - if (!keys.id) { - columns += `${columns.length ? ', ' : ''}id`; - } - if (!keys.pvalue) { - columns += `${columns.length ? ', ' : ''}pvalue`; - } - throw new Error(`Unable to find necessary column(s) for merge: ${columns} (available: ${keys._names_})`); - } - refVar = chain.body[findExtremeValue(chain.body, keys.pvalue)][keys.id]; - } - // Some datasets, notably the Portal, use a different marker format. - // Coerce it into one that will work with the LDServer API. (CHROM:POS_REF/ALT) - const REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/; - const match = refVar && refVar.match(REGEX_MARKER); - - if (!match) { - throw new Error('Could not request LD for a missing or incomplete marker format'); - } - const [original, chrom, pos, ref, alt] = match; - // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing - // a partial match at most leaves room for potential future features. - let refVar_formatted = `${chrom}:${pos}`; - if (ref && alt) { - refVar_formatted += `_${ref}/${alt}`; - } - - return [refVar_formatted, original]; - } - - /** - * Identify (or guess) the LD reference variant, then add query parameters to the URL to construct a query for the specified region - */ - getURL(state, chain, fields) { - // The LD source/pop can be overridden from plot.state for dynamic layouts - const build = state.genome_build || this.params.build || 'GRCh37'; // This isn't expected to change after the data is plotted. - let source = state.ld_source || this.params.source || '1000G'; - const population = state.ld_pop || this.params.population || 'ALL'; // LDServer panels will always have an ALL - const method = this.params.method || 'rsquare'; - - if (source === '1000G' && build === 'GRCh38') { - // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default. - source = '1000G-FRZ09'; - } - - validateBuildSource(this.constructor.name, build, null); // LD doesn't need to validate `source` option - - const [refVar_formatted, refVar_raw] = this.getRefvar(state, chain, fields); - - // Preserve the user-provided variant spec for use when matching to assoc data - chain.header.ldrefvar = refVar_raw; - - return [ - this.url, 'genome_builds/', build, '/references/', source, '/populations/', population, '/variants', - '?correlation=', method, - '&variant=', encodeURIComponent(refVar_formatted), - '&chrom=', encodeURIComponent(state.chr), - '&start=', encodeURIComponent(state.start), - '&stop=', encodeURIComponent(state.end), - ].join(''); - } - - /** - * The LD adapter caches based on region, reference panel, and population name - * @param state - * @param chain - * @param fields - * @return {string} - */ - getCacheKey(state, chain, fields) { - const base = super.getCacheKey(state, chain, fields); - let source = state.ld_source || this.params.source || '1000G'; - const population = state.ld_pop || this.params.population || 'ALL'; // LDServer panels will always have an ALL - const [refVar, _] = this.getRefvar(state, chain, fields); - return `${base}_${refVar}_${source}_${population}`; - } - - /** - * The LD adapter attempts to intelligently match retrieved LD information to a request for association data earlier in the data chain. - * Since each layer only asks for the data needed for that layer, one LD call is sufficient to annotate many separate association tracks. - */ - combineChainBody(data, chain, fields, outnames, trans) { - let keys = this.findMergeFields(chain); - let reqFields = this.findRequestedFields(fields, outnames); - if (!keys.position) { - throw new Error(`Unable to find position field for merge: ${keys._names_}`); - } - const leftJoin = function (left, right, lfield, rfield) { - let i = 0, j = 0; - while (i < left.length && j < right.position2.length) { - if (left[i][keys.position] === right.position2[j]) { - left[i][lfield] = right[rfield][j]; - i++; - j++; - } else if (left[i][keys.position] < right.position2[j]) { - i++; - } else { - j++; - } - } - }; - const tagRefVariant = function (data, refvar, idfield, outrefname, outldname) { - for (let i = 0; i < data.length; i++) { - if (data[i][idfield] && data[i][idfield] === refvar) { - data[i][outrefname] = 1; - data[i][outldname] = 1; // For label/filter purposes, implicitly mark the ref var as LD=1 to itself - } else { - data[i][outrefname] = 0; - } - } - }; - - // LD servers vary slightly. Some report corr as "rsquare", others as "correlation" - let corrField = data.rsquare ? 'rsquare' : 'correlation'; - leftJoin(chain.body, data, reqFields.ldout, corrField); - if (reqFields.isrefvarin && chain.header.ldrefvar) { - tagRefVariant(chain.body, chain.header.ldrefvar, keys.id, reqFields.isrefvarout, reqFields.ldout); - } - return chain.body; - } - - /** - * The LDServer API is paginated, but we need all of the data to render a plot. Depaginate and combine where appropriate. - */ - fetchRequest(state, chain, fields) { - let url = this.getURL(state, chain, fields); - let combined = { data: {} }; - let chainRequests = function (url) { - return fetch(url).then().then((response) => { - if (!response.ok) { - throw new Error(response.statusText); - } - return response.text(); - }).then(function(payload) { - payload = JSON.parse(payload); - Object.keys(payload.data).forEach(function (key) { - combined.data[key] = (combined.data[key] || []).concat(payload.data[key]); - }); - if (payload.next) { - return chainRequests(payload.next); - } - return combined; - }); - }; - return chainRequests(url); + _getURL (request_options) { + const {chr, start, end} = request_options; + const base = super._getURL(request_options); + return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`; } } + /** * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data. * There can be more than one claim per variant; this adapter is written to support a visualization in which each @@ -825,161 +308,78 @@

Source: data/adapters.js

* field in association data by looking for the field name `position` or `pos`. * * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter */ -class GwasCatalogLZ extends BaseApiAdapter { +class GwasCatalogLZ extends BaseUMAdapter { /** * @param {string} config.url The base URL for the remote data. - * @param {Object} config.params - * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant. + * @param [config.build] The genome build to use when requesting the specific genomic region. * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. - * @param {Number} [config.params.source] The ID of the chosen catalog. Most usages should omit this parameter and + * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37. */ constructor(config) { + if (!config.limit_fields) { + config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant']; + } super(config); - this.__dependentSource = true; } /** * Add query parameters to the URL to construct a query for the specified region */ - getURL(state, chain, fields) { - // This is intended to be aligned with another source- we will assume they are always ordered by position, asc - // (regardless of the actual match field) - const build = state.genome_build || this.params.build; - const source = this.params.source; - validateBuildSource(this.constructor.name, build, source); + _getURL(request_options) { + const build = request_options.genome_build || this._config.build; + const source = this._config.source; + this._validateBuildSource(build, source); // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build). // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date const source_query = build ? `&build=${build}` : ` and id eq ${source}`; - return `${this.url }?format=objects&sort=pos&filter=chrom eq '${state.chr}' and pos ge ${state.start} and pos le ${state.end}${source_query}`; - } - - findMergeFields(records) { - // Data from previous sources is already namespaced. Find the alignment field by matching. - const knownFields = Object.keys(records); - // Note: All API endoints involved only give results for 1 chromosome at a time; match is implied - const posMatch = knownFields.find(function (item) { - return item.match(/\b(position|pos)\b/i); - }); - - if (!posMatch) { - throw new Error('Could not find data to align with GWAS catalog results'); - } - return { 'pos': posMatch }; - } - extractFields (data, fields, outnames, trans) { - // Skip the "individual field extraction" step; extraction will be handled when building chain body instead - return data; - } - - /** - * Intelligently combine the LD data with the association data used for this data layer. See class description - * for a summary of how this works. - */ - combineChainBody(data, chain, fields, outnames, trans) { - if (!data.length) { - return chain.body; - } - - // TODO: Better reuse options in the future. This source is very specifically tied to the UM PortalDev API, where - // the field name is always "log_pvalue". Relatively few sites will write their own gwas-catalog endpoint. - const decider = 'log_pvalue'; - const decider_out = outnames[fields.indexOf(decider)]; - - function leftJoin(left, right, fields, outnames, trans) { // Add `fields` from `right` to `left` - // Add a synthetic, un-namespaced field to all matching records - const n_matches = left['n_catalog_matches'] || 0; - left['n_catalog_matches'] = n_matches + 1; - if (decider && left[decider_out] && left[decider_out] > right[decider]) { - // There may be more than one GWAS catalog entry for the same SNP. This source is intended for a 1:1 - // annotation scenario, so for now it only joins the catalog entry that has the best -log10 pvalue - return; - } - - for (let j = 0; j < fields.length; j++) { - const fn = fields[j]; - const outn = outnames[j]; - - let val = right[fn]; - if (trans && trans[j]) { - val = trans[j](val); - } - left[outn] = val; - } - } - - const chainNames = this.findMergeFields(chain.body[0]); - const catNames = this.findMergeFields(data[0]); - - var i = 0, j = 0; - while (i < chain.body.length && j < data.length) { - var left = chain.body[i]; - var right = data[j]; - - if (left[chainNames.pos] === right[catNames.pos]) { - // There may be multiple catalog entries for each matching SNP; evaluate match one at a time - leftJoin(left, right, fields, outnames, trans); - j += 1; - } else if (left[chainNames.pos] < right[catNames.pos]) { - i += 1; - } else { - j += 1; - } - } - return chain.body; + const base = super._getURL(request_options); + return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`; } } + /** * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format) * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter * @param {string} config.url The base URL for the remote data - * @param {Object} config.params - * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant. + * @param [config.build] The genome build to use * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. - * @param {Number} [config.params.source] The ID of the chosen gene dataset. Most usages should omit this parameter and + * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37. */ -class GeneLZ extends BaseApiAdapter { +class GeneLZ extends BaseUMAdapter { + constructor(config = {}) { + super(config); + + // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given. + // We will avoid transforming or modifying the payload. + this._prefix_namespace = false; + } + /** * Add query parameters to the URL to construct a query for the specified region */ - getURL(state, chain, fields) { - const build = state.genome_build || this.params.build; - let source = this.params.source; - validateBuildSource(this.constructor.name, build, source); + _getURL(request_options) { + const build = request_options.genome_build || this._config.build; + let source = this._config.source; + this._validateBuildSource(build, source); // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build). // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date const source_query = build ? `&build=${build}` : ` and source in ${source}`; - return `${this.url}?filter=chrom eq '${state.chr}' and start le ${state.end} and end ge ${state.start}${source_query}`; - } - - /** - * The UM genes API has a very complex internal data format. Bypass any record parsing, and provide the data layer with - * the exact information returned by the API. (ignoring the fields array in the layout) - * @param data - * @return {Object[]|Object} - */ - normalizeResponse(data) { - return data; - } - /** - * Does not attempt to namespace or modify the fields from the API payload; the payload format is very complex and - * quite coupled with the data rendering implementation. - * Typically, requests to this adapter specify a single dummy field sufficient to trigger the request: `fields:[ 'gene:all' ]` - */ - extractFields(data, fields, outnames, trans) { - return data; + const base = super._getURL(request_options); + return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`; } } + /** * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint) * @@ -988,67 +388,55 @@

Source: data/adapters.js

* matching on specific assumptions about `gene_name` format. * * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter */ -class GeneConstraintLZ extends BaseApiAdapter { +class GeneConstraintLZ extends BaseLZAdapter { /** * @param {string} config.url The base URL for the remote data - * @param {Object} config.params - * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant. + * @param [config.build] The genome build to use * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. */ - constructor(config) { + constructor(config = {}) { super(config); - this.__dependentSource = true; + this._prefix_namespace = false; } - /** - * GraphQL API: request details are encoded in the body, not the URL - */ - getURL() { - return this.url; - } - - /** - * The gnomAD API has a very complex internal data format. Bypass any record parsing, and provide the data layer with - * the exact information returned by the API. - */ - normalizeResponse(data) { - return data; - } - - fetchRequest(state, chain, fields) { - const build = state.genome_build || this.params.build; + _buildRequestOptions(state, genes_data) { + const build = state.genome_build || this._config.build; if (!build) { throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`); } - const unique_gene_names = chain.body.reduce( + const unique_gene_names = new Set(); + for (let gene of genes_data) { // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the // gene names to avoid issuing a malformed GraphQL query. - function (acc, gene) { - acc[gene.gene_name] = null; - return acc; - }, - {} - ); - let query = Object.keys(unique_gene_names).map(function (gene_name) { + unique_gene_names.add(gene.gene_name); + } + + state.query = [...unique_gene_names.values()].map(function (gene_name) { // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268 const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // Each gene symbol is a separate graphQL query, grouped into one request using aliases return `${alias}: gene(gene_symbol: "${gene_name}", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `; }); + state.build = build; + return Object.assign({}, state); + } + _performRequest(options) { + let {query, build} = options; if (!query.length || query.length > 25 || build === 'GRCh38') { // Skip the API request when it would make no sense: // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.) - // - Too many genes (gnomAD appears max cost ~25 genes) + // - Too many genes (gnomAD appears to set max cost ~25 genes) // - No genes in region (hence no constraint info) - return Promise.resolve({ data: null }); + return Promise.resolve([]); } - query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas - const url = this.getURL(state, chain, fields); + + const url = this._getURL(options); + // See: https://graphql.org/learn/serving-over-http/ const body = JSON.stringify({ query: query }); const headers = { 'Content-Type': 'application/json' }; @@ -1064,62 +452,239 @@

Source: data/adapters.js

} /** - * Annotate GENCODE data (from a previous request to the genes adapter) with additional gene constraint data from - * the gnomAD API. See class description for a summary of how this works. + * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps. */ - combineChainBody(data, chain, fields, outnames, trans) { - if (!data) { - return chain; + _normalizeResponse(response_text) { + if (typeof response_text !== 'string') { + // If the query short-circuits, we receive an empty list instead of a string + return response_text; } + const data = JSON.parse(response_text); + return data.data; + } +} - chain.body.forEach(function(gene) { - // Find payload keys that match gene names in this response - const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names - const constraint = data[alias] && data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene - if (constraint) { - // Add all fields from constraint data- do not override fields present in the gene source - Object.keys(constraint).forEach(function (key) { - let val = constraint[key]; - if (typeof gene[key] === 'undefined') { - if (typeof val == 'number' && val.toString().includes('.')) { - val = parseFloat(val.toFixed(2)); - } - gene[key] = val; // These two sources are both designed to bypass namespacing - } - }); + +/** + * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant. + * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS + * variant and yse that as the LD reference variant. + * + * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly + * if this information is not provided. + * + * This source is designed to connect its results to association data, and therefore depends on association data having + * been loaded by a previous request. For custom association APIs, some additional options might + * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt` + * are preferred, but this source will attempt to harmonize other common data formats into something that the LD + * server can understand. + * + * @public + * @see module:LocusZoom_Adapters~BaseUMAdapter + */ +class LDServer extends BaseUMAdapter { + /** + * @param {string} config.url The base URL for the remote data. + * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant. + * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. + * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance. + * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display. + * @param [config.population='ALL'] The sample population used to calculate LD for a specified source; + * population names vary depending on the reference panel and how the server was populated wth data. + * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display. + * @param [config.method='rsquare'] The metric used to calculate LD + */ + constructor(config) { + if (!config.limit_fields) { + config.limit_fields = ['variant2', 'position2', 'correlation']; + } + super(config); + } + + __find_ld_refvar(state, assoc_data) { + const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant'); + const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue'); + + // Determine the reference variant (via user selected OR automatic-per-track) + let refvar; + let best_hit = {}; + if (state.ldrefvar) { + // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data + refvar = state.ldrefvar; + best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {}; + } else { + // find highest log-value and associated var spec + let best_logp = 0; + for (let item of assoc_data) { + const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item; + if (log_pvalue > best_logp) { + best_logp = log_pvalue; + refvar = variant; + best_hit = item; + } } - }); - return chain.body; + } + + // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting. + // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase. + best_hit.lz_is_ld_refvar = true; + + // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server, + // the variant fields must be normalized to a specific format. All later LD operations will use that format. + const match = parseMarker(refvar, true); + if (!match) { + throw new Error('Could not request LD for a missing or incomplete marker format'); + } + + const [chrom, pos, ref, alt] = match; + // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing + // a partial match at most leaves room for potential future features. + refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip? + if (ref && alt) { + refvar += `_${ref}/${alt}`; + } + + const coord = +pos; + // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably + // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby. + if ((coord && state.ldrefvar && state.chr) && (chrom !== state.chr || coord < state.start || coord > state.end)) { + // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a + // *copy* of plot.state, so wiping here doesn't remove the original value. + state.ldrefvar = null; + return this.__find_ld_refvar(state, assoc_data); + } + + // Return the reference variant, in a normalized format suitable for LDServer queries + return refvar; + } + + _buildRequestOptions(state, assoc_data) { + if (!assoc_data) { + throw new Error('LD request must depend on association data'); + } + + // If no state refvar is provided, find the most significant variant in any provided assoc data. + // Assumes that assoc satisfies the "assoc" fields contract, eg has fields variant and log_pvalue + const base = super._buildRequestOptions(...arguments); + if (!assoc_data.length) { + // No variants, so no need to annotate association data with LD! + // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region) + base._skip_request = true; + return base; + } + + base.ld_refvar = this.__find_ld_refvar(state, assoc_data); + + // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config + const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted. + let ld_source = state.ld_source || this._config.source || '1000G'; + const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL + + if (ld_source === '1000G' && genome_build === 'GRCh38') { + // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default. + ld_source = '1000G-FRZ09'; + } + + this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option + return Object.assign({}, base, { genome_build, ld_source, ld_population }); + } + + _getURL(request_options) { + const method = this._config.method || 'rsquare'; + const { + chr, start, end, + ld_refvar, + genome_build, ld_source, ld_population, + } = request_options; + + const base = super._getURL(request_options); + + return [ + base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants', + '?correlation=', method, + '&variant=', encodeURIComponent(ld_refvar), + '&chrom=', encodeURIComponent(chr), + '&start=', encodeURIComponent(start), + '&stop=', encodeURIComponent(end), + ].join(''); + } + + _getCacheKey(options) { + // LD is keyed by more than just region; append other parameters to the base cache key + const base = super._getCacheKey(options); + const { ld_refvar, ld_source, ld_population } = options; + return `${base}_${ld_refvar}_${ld_source}_${ld_population}`; + } + + _performRequest(options) { + // Skip request if this one depends on other data, and we are in a region with no data + if (options._skip_request) { + // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird. + return Promise.resolve([]); + } + + const url = this._getURL(options); + + // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected + let combined = { data: {} }; + let chainRequests = function (url) { + return fetch(url).then().then((response) => { + if (!response.ok) { + throw new Error(response.statusText); + } + return response.text(); + }).then(function(payload) { + payload = JSON.parse(payload); + Object.keys(payload.data).forEach(function (key) { + combined.data[key] = (combined.data[key] || []).concat(payload.data[key]); + }); + if (payload.next) { + return chainRequests(payload.next); + } + return combined; + }); + }; + return chainRequests(url); } } + /** * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible) * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter * @param {string} config.url The base URL for the remote data - * @param {Object} config.params - * @param [config.params.build] The genome build to use when calculating LD relative to a specified reference variant. + * @param [config.build] The genome build to use * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way. - * @param {Number} [config.params.source] The ID of the chosen dataset. Most usages should omit this parameter and + * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37. */ -class RecombLZ extends BaseApiAdapter { +class RecombLZ extends BaseUMAdapter { + constructor(config) { + if (!config.limit_fields) { + config.limit_fields = ['position', 'recomb_rate']; + } + super(config); + } + /** * Add query parameters to the URL to construct a query for the specified region */ - getURL(state, chain, fields) { - const build = state.genome_build || this.params.build; - let source = this.params.source; - validateBuildSource(this.constructor.SOURCE_NAME, build, source); + _getURL(request_options) { + const build = request_options.genome_build || this._config.build; + let source = this._config.source; + this._validateBuildSource(build, source); // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build). // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date const source_query = build ? `&build=${build}` : ` and id in ${source}`; - return `${this.url}?filter=chromosome eq '${state.chr}' and position le ${state.end} and position ge ${state.start}${source_query}`; + + const base = super._getURL(request_options); + return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`; } } + /** * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required * for some sources (eg it does not know how to join together LD and association data). @@ -1132,38 +697,44 @@

Source: data/adapters.js

* * Note: The name is a bit misleading. It receives JS objects, not strings serialized as "json". * @public - * @see module:LocusZoom_Adapters~BaseAdapter - * @param {object} data The data to be returned by this source (subject to namespacing rules) + * @see module:LocusZoom_Adapters~BaseLZAdapter + * @param {object} config.data The data to be returned by this source (subject to namespacing rules) */ -class StaticSource extends BaseAdapter { - parseInit(data) { +class StaticSource extends BaseLZAdapter { + constructor(config = {}) { // Does not receive any config; the only argument is the raw data, embedded when source is created + super(...arguments); + const { data } = config; + if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key + throw new Error("'StaticSource' must provide data as required option 'config.data'"); + } this._data = data; } - getRequest(state, chain, fields) { + _performRequest(options) { return Promise.resolve(this._data); } } + /** * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API * @public - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter * @param {string} config.url The base URL for the remote data - * @param {Object} config.params - * @param {String[]} config.params.build This datasource expects to be provided the name of the genome build that will - * be used to provide pheWAS results for this position. Note positions may not translate between builds. + * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will + * be used to provide PheWAS results for this position. Note positions may not translate between builds. */ -class PheWASLZ extends BaseApiAdapter { - getURL(state, chain, fields) { - const build = (state.genome_build ? [state.genome_build] : null) || this.params.build; +class PheWASLZ extends BaseUMAdapter { + _getURL(request_options) { + const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build; if (!build || !Array.isArray(build) || !build.length) { - throw new Error(['Adapter', this.constructor.SOURCE_NAME, 'requires that you specify array of one or more desired genome build names'].join(' ')); + throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' ')); } + const base = super._getURL(request_options); const url = [ - this.url, - "?filter=variant eq '", encodeURIComponent(state.variant), "'&format=objects&", + base, + "?filter=variant eq '", encodeURIComponent(request_options.variant), "'&format=objects&", build.map(function (item) { return `build=${encodeURIComponent(item)}`; }).join('&'), @@ -1171,108 +742,21 @@

Source: data/adapters.js

return url.join(''); } - getCacheKey(state, chain, fields) { - // This is not a region-based source; it doesn't make sense to cache by a region - return this.getURL(state, chain, fields); - } -} - -/** - * Base class for "connectors"- this is a highly specialized kind of adapter that is rarely used in most LocusZoom - * deployments. This is meant to be subclassed, rather than used directly. - * - * A connector is a data adapter that makes no server requests and caches no data of its own. Instead, it decides how to - * combine data from other sources in the chain. Connectors are useful when we want to request (or calculate) some - * useful piece of information once, but apply it to many different kinds of record types. - * - * Typically, a subclass will implement the field merging logic in `combineChainBody`. - * - * @public - * @see module:LocusZoom_Adapters~BaseAdapter - */ -class ConnectorSource extends BaseAdapter { - /** - * @param {Object} config.params Additional parameters - * @param {Object} config.params.sources Specify how the hard-coded logic should find the data it relies on in the chain, - * as {internal_name: chain_source_id} pairs. This allows writing a reusable connector that does not need to make - * assumptions about what namespaces a source is using. * - */ - constructor(config) { - super(config); - - if (!config || !config.sources) { - throw new Error('Connectors must specify the data they require as config.sources = {internal_name: chain_source_id}} pairs'); - } - - /** - * Tells the connector how to find the data it relies on - * - * For example, a connector that applies burden test information to the genes layer might specify: - * {gene_ns: "gene", aggregation_ns: "aggregation"} - * - * @member {Object} - * @private - */ - this._source_name_mapping = config.sources; - - // Validate that this source has been told how to find the required information - const specified_ids = Object.keys(config.sources); - /** @property {String[]} Specifies the sources that must be provided in the original config object */ - - this._getRequiredSources().forEach((k) => { - if (!specified_ids.includes(k)) { - // TODO: Fix constructor.name usage in minified bundles - throw new Error(`Configuration for ${this.constructor.name} must specify a source ID corresponding to ${k}`); - } - }); - } - - // Stub- connectors don't have their own url or data, so the defaults don't make sense - parseInit() {} - - getRequest(state, chain, fields) { - // Connectors do not request their own data by definition, but they *do* depend on other sources having been loaded - // first. This method performs basic validation, and preserves the accumulated body from the chain so far. - Object.keys(this._source_name_mapping).forEach((ns) => { - const chain_source_id = this._source_name_mapping[ns]; - if (chain.discrete && !chain.discrete[chain_source_id]) { - throw new Error(`${this.constructor.name} cannot be used before loading required data for: ${chain_source_id}`); - } - }); - return Promise.resolve(chain.body || []); - } - - parseResponse(data, chain, fields, outnames, trans) { - // A connector source does not update chain.discrete, but it may use it. It bypasses data formatting - // and field selection (both are assumed to have been done already, by the previous sources this draws from) - - // Because of how the chain works, connectors are not very good at applying new transformations or namespacing. - // Typically connectors are called with `connector_name:all` in the fields array. - return Promise.resolve(this.combineChainBody(data, chain, fields, outnames, trans)) - .then(function(new_body) { - return {header: chain.header || {}, discrete: chain.discrete || {}, body: new_body}; - }); - } - - combineChainBody(records, chain) { - // Stub method: specifies how to combine the data - throw new Error('This method must be implemented in a subclass'); - } - - /** - * Helper method since ES6 doesn't support class fields - * @private - */ - _getRequiredSources() { - throw new Error('Must specify an array that identifes the kind of data required by this source'); + _getCacheKey(options) { + // Not a region based source; don't do anything smart for cache check + return this._getURL(options); } } +// Deprecated symbols export { BaseAdapter, BaseApiAdapter }; +// Usually used as a parent class for custom code +export { BaseLZAdapter, BaseUMAdapter }; + +// Usually used as a standalone class export { AssociationLZ, - ConnectorSource, GeneConstraintLZ, GeneLZ, GwasCatalogLZ, @@ -1291,7 +775,7 @@

Source: data/adapters.js


diff --git a/docs/api/data_field.js.html b/docs/api/data_field.js.html index 2c2f956a..083b0136 100644 --- a/docs/api/data_field.js.html +++ b/docs/api/data_field.js.html @@ -26,7 +26,7 @@

Source: data/field.js

-
import transforms from '../registry/transforms';
+            
import TRANSFORMS from '../registry/transforms';
 
 /**
  * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.
@@ -43,20 +43,18 @@ 

Source: data/field.js

*/ class Field { constructor(field) { - const parts = /^(?:([^:]+):)?([^:|]*)(\|.+)*$/.exec(field); - /** @member {String} */ - this.full_name = field; - /** @member {String} */ - this.namespace = parts[1] || null; - /** @member {String} */ - this.name = parts[2] || null; - /** @member {Array} */ - this.transformations = []; - - if (typeof parts[3] == 'string' && parts[3].length > 1) { - this.transformations = parts[3].substring(1).split('|'); - this.transformations.forEach((transform, i) => this.transformations[i] = transforms.get(transform)); + // Two scenarios: we are requesting a field by full name, OR there are transforms to apply + // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN` + const field_pattern = /^(?:\w+:\w+|^\w+)(?:\|\w+)*$/; + if (!field_pattern.test(field)) { + throw new Error(`Invalid field specifier: '${field}'`); } + + const [name, ...transforms] = field.split('|'); + + this.full_name = field; // fieldname + transforms + this.field_name = name; // just fieldname + this.transformations = transforms.map((name) => TRANSFORMS.get(name)); } _applyTransformations(val) { @@ -76,15 +74,14 @@

Source: data/field.js

* @returns {*} */ resolve(data, extra) { + // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null if (typeof data[this.full_name] == 'undefined') { // Check for cached result let val = null; - if (typeof (data[`${this.namespace}:${this.name}`]) != 'undefined') { // Fallback: value sans transforms - val = data[`${this.namespace}:${this.name}`]; - } else if (typeof data[this.name] != 'undefined') { // Fallback: value present without namespace - val = data[this.name]; - } else if (extra && typeof extra[this.full_name] != 'undefined') { // Fallback: check annotations - val = extra[this.full_name]; - } // We should really warn if no value found, but many bad layouts exist and this could break compatibility + if (data[this.field_name] !== undefined) { // Fallback: value sans transforms + val = data[this.field_name]; + } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations + val = extra[this.field_name]; + } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations) data[this.full_name] = this._applyTransformations(val); } return data[this.full_name]; @@ -102,7 +99,7 @@

Source: data/field.js


diff --git a/docs/api/data_requester.js.html b/docs/api/data_requester.js.html index 0b27b179..ee7d3104 100644 --- a/docs/api/data_requester.js.html +++ b/docs/api/data_requester.js.html @@ -26,17 +26,51 @@

Source: data/requester.js

-
import { TRANSFORMS } from '../registry';
+            
/**
+ * @module
+ * @private
+ */
+import {getLinkedData} from 'undercomplicate';
+
+import { DATA_OPS } from '../registry';
+
+
+class DataOperation {
+    /**
+     * Perform a data operation (such as a join)
+     * @param {String} join_type
+     * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!
+     * @param params Optional user/layout parameters to be passed to the data function
+     */
+    constructor(join_type, initiator, params) {
+        this._callable = DATA_OPS.get(join_type);
+        this._initiator = initiator;
+        this._params = params || [];
+    }
+
+    getData(plot_state, ...dependent_recordsets) {
+        // Most operations are joins: they receive two pieces of data (eg left + right)
+        //   Other ops are possible, like consolidating just one set of records to best value per key
+        // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]
+
+        // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options
+        const context = {plot_state, data_layer: this._initiator};
+        return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));
+    }
+}
+
 
 /**
  * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.
- *   It passes state information and ensures that data is formatted in the manner expected by the plot.
+ *   It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a
+ *   designated order.
+ *
+ * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can
+ *   perform its own calculations, filter results, and apply transforms without influencing other layers.
+ *  (while still respecting a shared cache where appropriate)
  *
  * This object is not part of the public interface. It should almost **never** be replaced or modified directly.
  *
- * It is also responsible for constructing a "chain" of dependent requests, by requesting each datasource
- *   sequentially in the order specified in the datalayer `fields` array. Data sources are only chained within a
- *   data layer, and only if that layer requests more than one source of data.
  * @param {DataSources} sources A set of data sources used specifically by this plot instance
  * @private
  */
@@ -45,60 +79,111 @@ 

Source: data/requester.js

this._sources = sources; } - __split_requests(fields) { - // Given a fields array, return an object specifying what datasource names the data layer should make requests - // to, and how to handle the returned data - var requests = {}; - // Regular expression finds namespace:field|trans - var re = /^(?:([^:]+):)?([^:|]*)(\|.+)*$/; - fields.forEach(function(raw) { - var parts = re.exec(raw); - var ns = parts[1] || 'base'; - var field = parts[2]; - var trans = TRANSFORMS.get(parts[3]); - if (typeof requests[ns] == 'undefined') { - requests[ns] = {outnames:[], fields:[], trans:[]}; + /** + * Parse the data layer configuration when a layer is first created. + * Validate config, and return entities and dependencies in a format usable for data retrieval. + * This is used by data layers, and also other data-retrieval functions (like subscribeToDate). + * + * Inherent assumptions: + * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed. + * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be + * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the + * removed adapter. + * @param {Object} namespace_options + * @param {Array} data_operations + * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations, + * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate + * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care! + * @returns {Array} Map of entities and list of dependencies + */ + config_to_sources(namespace_options = {}, data_operations = [], initiator) { + const entities = new Map(); + const namespace_local_names = Object.keys(namespace_options); + + // 1. Specify how to coordinate data. Precedence: + // a) EXPLICIT fetch logic, + // b) IMPLICIT auto-generate fetch order if there is only one NS, + // c) Throw "spec required" error if > 1, because 2 adapters may need to be fetched in a sequence + let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from} + if (!dependency_order) { + dependency_order = { type: 'fetch', from: namespace_local_names }; + data_operations.unshift(dependency_order); + } + + // Validate that all NS items are available to the root requester in DataSources. All layers recognize a + // default value, eg people copying the examples tend to have defined a datasource called "assoc" + const ns_pattern = /^\w+$/; + for (let [local_name, global_name] of Object.entries(namespace_options)) { + if (!ns_pattern.test(local_name)) { + throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`); + } + + const source = this._sources.get(global_name); + if (!source) { + throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`); + } + entities.set(local_name, source); + + // Note: Dependency spec checker will consider "ld(assoc)" to match a namespace called "ld" + if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) { + // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join. + // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch + // Thus the default behavior is "fetch all namespaces as though they don't depend on anything. + // If they depend on something, only then does "data_ops[@type=fetch].from" need to be mutated + dependency_order.from.push(local_name); } - requests[ns].outnames.push(raw); - requests[ns].fields.push(field); - requests[ns].trans.push(trans); - }); - return requests; + } + + let dependencies = Array.from(dependency_order.from); + + // Now check all joins. Are namespaces valid? Are they requesting known data? + for (let config of data_operations) { + let {type, name, requires, params} = config; + if (type !== 'fetch') { + let namecount = 0; + if (!name) { + name = config.name = `join${namecount}`; + namecount += 1; + } + + if (entities.has(name)) { + throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`); + } + requires.forEach((require_name) => { + if (!entities.has(require_name)) { + throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`); + } + }); + + const task = new DataOperation(type, initiator, params); + entities.set(name, task); + dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB) + } + } + return [entities, dependencies]; } /** - * Fetch data, and create a chain that only connects two data sources if they depend on each other - * @param {Object} state The current "state" of the plot, such as chromosome and start/end positions - * @param {String[]} fields The list of data fields specified in the `layout` for a specific data layer + * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end) + * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts. + * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances + * (things that implement a method getData). + * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order * @returns {Promise} */ - getData(state, fields) { - var requests = this.__split_requests(fields); - // Create an array of functions that, when called, will trigger the request to the specified datasource - var request_handles = Object.keys(requests).map((key) => { - if (!this._sources.get(key)) { - throw new Error(`Datasource for namespace ${key} not found`); - } - return this._sources.get(key).getData( - state, - requests[key].fields, - requests[key].outnames, - requests[key].trans - ); - }); - //assume the fields are requested in dependent order - //TODO: better manage dependencies - var ret = Promise.resolve({header:{}, body: [], discrete: {}}); - for (var i = 0; i < request_handles.length; i++) { - // If a single datalayer uses multiple sources, perform the next request when the previous one completes - ret = ret.then(request_handles[i]); + getData(plot_state, entities, dependencies) { + if (!dependencies.length) { + return Promise.resolve([]); } - return ret; + // The last dependency (usually the last join operation) determines the last thing returned. + return getLinkedData(plot_state, entities, dependencies, true); } } export default Requester; + +export {DataOperation as _JoinTask};
@@ -109,7 +194,7 @@

Source: data/requester.js


diff --git a/docs/api/data_sources.js.html b/docs/api/data_sources.js.html index fa20c2fc..f79bea5a 100644 --- a/docs/api/data_sources.js.html +++ b/docs/api/data_sources.js.html @@ -92,7 +92,7 @@

Source: data/sources.js


diff --git a/docs/api/ext_lz-credible-sets.js.html b/docs/api/ext_lz-credible-sets.js.html index aa3fc601..d5127304 100644 --- a/docs/api/ext_lz-credible-sets.js.html +++ b/docs/api/ext_lz-credible-sets.js.html @@ -43,7 +43,6 @@

Source: ext/lz-credible-sets.js

* ### Loading and usage * The page must incorporate and load all libraries before this file can be used, including: * - LocusZoom - * - gwas-credible-sets (available via NPM or a related CDN) * * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies): * ``` @@ -62,7 +61,7 @@

Source: ext/lz-credible-sets.js

import {marking, scoring} from 'gwas-credible-sets'; function install (LocusZoom) { - const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter'); + const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter'); /** * (**extension**) Custom data adapter that calculates the 95% credible set based on provided association data. @@ -71,9 +70,8 @@

Source: ext/lz-credible-sets.js

* @alias module:LocusZoom_Adapters~CredibleSetLZ * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions */ - class CredibleSetLZ extends BaseAdapter { + class CredibleSetLZ extends BaseUMAdapter { /** - * @param {String} config.params.fields.log_pvalue The name of the field containing -log10 pvalue information * @param {Number} [config.params.threshold=0.95] The credible set threshold (eg 95%). Will continue selecting SNPs * until the posterior probabilities add up to at least this fraction of the total. * @param {Number} [config.params.significance_threshold=7.301] Do not perform a credible set calculation for this @@ -83,49 +81,45 @@

Source: ext/lz-credible-sets.js

*/ constructor(config) { super(...arguments); - this.dependentSource = true; // Don't do calcs for a region with no assoc data - } - - parseInit(config) { - super.parseInit(...arguments); - if (!(this.params.fields && this.params.fields.log_pvalue)) { - throw new Error(`Source config for ${this.constructor.SOURCE_NAME} must specify how to find 'fields.log_pvalue'`); - } - // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p) - this.params = Object.assign( + this._config = Object.assign( { threshold: 0.95, significance_threshold: 7.301 }, - this.params + this._config ); + this._prefix_namespace = false; } - getCacheKey (state, chain, fields) { - const threshold = state.credible_set_threshold || this.params.threshold; + _getCacheKey (state) { + const threshold = state.credible_set_threshold || this._config.threshold; return [threshold, state.chr, state.start, state.end].join('_'); } - fetchRequest(state, chain) { - if (!chain.body.length) { + _buildRequestOptions(options, assoc_data) { + const base = super._buildRequestOptions(...arguments); + base._assoc_data = assoc_data; + return base; + } + + _performRequest(options) { + const {_assoc_data} = options; + if (!_assoc_data.length) { // No credible set can be calculated because there is no association data for this region return Promise.resolve([]); } - const self = this; - // The threshold can be overridden dynamically via `plot.state`, or set when the source is created - const threshold = state.credible_set_threshold || this.params.threshold; + const assoc_logp_name = this._findPrefixedKey(_assoc_data[0], 'log_pvalue'); + + const threshold = this._config.threshold; + // Calculate raw bayes factors and posterior probabilities based on information returned from the API - if (typeof chain.body[0][self.params.fields.log_pvalue] === 'undefined') { - throw new Error('Credible set source could not locate the required fields from a previous request.'); - } - const nlogpvals = chain.body.map((item) => item[self.params.fields.log_pvalue]); + const nlogpvals = _assoc_data.map((item) => item[assoc_logp_name]); - if (!nlogpvals.some((val) => val >= self.params.significance_threshold)) { + if (!nlogpvals.some((val) => val >= this._config.significance_threshold)) { // If NO points have evidence of significance, define the credible set to be empty // (rather than make a credible set that we don't think is meaningful) - return Promise.resolve([]); + return Promise.resolve(_assoc_data); } - const credset_data = []; try { const scores = scoring.bayesFactors(nlogpvals); const posteriorProbabilities = scoring.normalizeProbabilities(scores); @@ -136,33 +130,18 @@

Source: ext/lz-credible-sets.js

const credSetScaled = marking.rescaleCredibleSet(credibleSet); const credSetBool = marking.markBoolean(credibleSet); - // Annotate each response record based on credible set membership - for (let i = 0; i < chain.body.length; i++) { - credset_data.push({ - posterior_prob: posteriorProbabilities[i], - contrib_fraction: credSetScaled[i], - is_member: credSetBool[i], - }); + // Annotate each response record based on credible set membership. This has the effect of joining + // credset results to assoc data directly within the adapter (no separate join needed) + for (let i = 0; i < _assoc_data.length; i++) { + _assoc_data[i][`${options._provider_name}:posterior_prob`] = posteriorProbabilities[i]; + _assoc_data[i][`${options._provider_name}:contrib_fraction`] = credSetScaled[i]; + _assoc_data[i][`${options._provider_name}:is_member`] = credSetBool[i]; } } catch (e) { // If the calculation cannot be completed, return the data without annotation fields console.error(e); } - return Promise.resolve(credset_data); - } - - combineChainBody(data, chain, fields, outnames, trans) { - // At this point namespacing has been applied; add the calculated fields for this source to the chain - if (chain.body.length && data.length) { - for (let i = 0; i < data.length; i++) { - const src = data[i]; - const dest = chain.body[i]; - Object.keys(src).forEach(function (attr) { - dest[attr] = src[attr]; - }); - } - } - return chain.body; + return Promise.resolve(_assoc_data); } } @@ -177,8 +156,8 @@

Source: ext/lz-credible-sets.js

*/ const association_credible_set_tooltip = function () { // Extend a known tooltip with an extra row of info showing posterior probabilities - const l = LocusZoom.Layouts.get('tooltip', 'standard_association', { unnamespaced: true }); - l.html += '{{#if {{namespace[credset]}}posterior_prob}}<br>Posterior probability: <strong>{{{{namespace[credset]}}posterior_prob|scinotation|htmlescape}}</strong>{{/if}}'; + const l = LocusZoom.Layouts.get('tooltip', 'standard_association'); + l.html += '{{#if credset:posterior_prob}}<br>Posterior probability: <strong>{{credset:posterior_prob|scinotation|htmlescape}}</strong>{{/if}}'; return l; }(); @@ -191,13 +170,12 @@

Source: ext/lz-credible-sets.js

* @see {@link module:ext/lz-credible-sets} for required extension and installation instructions */ const annotation_credible_set_tooltip = { - namespace: { 'assoc': 'assoc', 'credset': 'credset' }, closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: '<strong>{{{{namespace[assoc]}}variant|htmlescape}}</strong><br>' - + 'P Value: <strong>{{{{namespace[assoc]}}log_pvalue|logtoscinotation|htmlescape}}</strong><br>' + - '{{#if {{namespace[credset]}}posterior_prob}}<br>Posterior probability: <strong>{{{{namespace[credset]}}posterior_prob|scinotation|htmlescape}}</strong>{{/if}}', + html: '<strong>{{assoc:variant|htmlescape}}</strong><br>' + + 'P Value: <strong>{{assoc:log_pvalue|logtoscinotation|htmlescape}}</strong><br>' + + '{{#if credset:posterior_prob}}<br>Posterior probability: <strong>{{credset:posterior_prob|scinotation|htmlescape}}</strong>{{/if}}', }; LocusZoom.Layouts.add('tooltip', 'annotation_credible_set', annotation_credible_set_tooltip); @@ -210,20 +188,23 @@

Source: ext/lz-credible-sets.js

const association_credible_set_layer = function () { const base = LocusZoom.Layouts.get('data_layer', 'association_pvalues', { - unnamespaced: true, id: 'associationcredibleset', namespace: { 'assoc': 'assoc', 'credset': 'credset', 'ld': 'ld' }, - fill_opacity: 0.7, - tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set', { unnamespaced: true }), - fields: [ - '{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', - '{{namespace[assoc]}}log_pvalue', '{{namespace[assoc]}}log_pvalue|logtoscinotation', - '{{namespace[assoc]}}ref_allele', - '{{namespace[credset]}}posterior_prob', '{{namespace[credset]}}contrib_fraction', - '{{namespace[credset]}}is_member', - '{{namespace[ld]}}state', '{{namespace[ld]}}isrefvar', + data_operations: [ + { + type: 'fetch', + from: ['assoc', 'ld(assoc)', 'credset(assoc)'], + }, + { + type: 'left_match', + name: 'credset_plus_ld', + requires: ['credset', 'ld'], // The credible sets demo wasn't fully moved over to the new data operations system, and as such it is a bit weird + params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later. + }, ], - match: { send: '{{namespace[assoc]}}variant', receive: '{{namespace[assoc]}}variant' }, + fill_opacity: 0.7, + tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set'), + match: { send: 'assoc:variant', receive: 'assoc:variant' }, }); base.color.unshift({ field: 'lz_is_match', // Special field name whose presence triggers custom rendering @@ -245,11 +226,15 @@

Source: ext/lz-credible-sets.js

*/ const annotation_credible_set_layer = { namespace: { 'assoc': 'assoc', 'credset': 'credset' }, + data_operations: [{ + type: 'fetch', + from: ['assoc', 'credset(assoc)'], + }], id: 'annotationcredibleset', type: 'annotation_track', - id_field: '{{namespace[assoc]}}variant', + id_field: 'assoc:variant', x_axis: { - field: '{{namespace[assoc]}}position', + field: 'assoc:position', }, color: [ { @@ -262,11 +247,10 @@

Source: ext/lz-credible-sets.js

}, '#00CC00', ], - fields: ['{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', '{{namespace[assoc]}}log_pvalue', '{{namespace[credset]}}posterior_prob', '{{namespace[credset]}}contrib_fraction', '{{namespace[credset]}}is_member'], - match: { send: '{{namespace[assoc]}}variant', receive: '{{namespace[assoc]}}variant' }, + match: { send: 'assoc:variant', receive: 'assoc:variant' }, filters: [ // Specify which points to show on the track. Any selection must satisfy ALL filters - { field: '{{namespace[credset]}}is_member', operator: '=', value: true }, + { field: 'credset:is_member', operator: '=', value: true }, ], behaviors: { onmouseover: [ @@ -282,7 +266,7 @@

Source: ext/lz-credible-sets.js

{ action: 'toggle', status: 'selected' }, ], }, - tooltip: LocusZoom.Layouts.get('tooltip', 'annotation_credible_set', { unnamespaced: true }), + tooltip: LocusZoom.Layouts.get('tooltip', 'annotation_credible_set'), tooltip_positioning: 'top', }; LocusZoom.Layouts.add('data_layer', 'annotation_credible_set', annotation_credible_set_layer); @@ -300,7 +284,7 @@

Source: ext/lz-credible-sets.js

height: 50, margin: { top: 25, right: 50, bottom: 10, left: 50 }, inner_border: 'rgb(210, 210, 210)', - toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel', { unnamespaced: true }), + toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel'), axes: { x: { extent: 'state', render: false }, }, @@ -310,7 +294,7 @@

Source: ext/lz-credible-sets.js

x_linked: true, }, data_layers: [ - LocusZoom.Layouts.get('data_layer', 'annotation_credible_set', { unnamespaced: true }), + LocusZoom.Layouts.get('data_layer', 'annotation_credible_set'), ], }; LocusZoom.Layouts.add('panel', 'annotation_credible_set', annotation_credible_set); @@ -323,13 +307,11 @@

Source: ext/lz-credible-sets.js

*/ const association_credible_set_panel = function () { const l = LocusZoom.Layouts.get('panel', 'association', { - unnamespaced: true, id: 'associationcrediblesets', - namespace: { 'assoc': 'assoc', 'credset': 'credset' }, data_layers: [ - LocusZoom.Layouts.get('data_layer', 'significance', { unnamespaced: true }), - LocusZoom.Layouts.get('data_layer', 'recomb_rate', { unnamespaced: true }), - LocusZoom.Layouts.get('data_layer', 'association_credible_set', { unnamespaced: true }), + LocusZoom.Layouts.get('data_layer', 'significance'), + LocusZoom.Layouts.get('data_layer', 'recomb_rate'), + LocusZoom.Layouts.get('data_layer', 'association_credible_set'), ], }); // Add "display options" button to control how credible set coloring is overlaid on the standard association plot @@ -352,7 +334,7 @@

Source: ext/lz-credible-sets.js

point_shape: 'circle', point_size: 40, color: { - field: '{{namespace[credset]}}is_member', + field: 'credset:is_member', scale_function: 'if', parameters: { field_value: true, @@ -386,7 +368,7 @@

Source: ext/lz-credible-sets.js

point_size: 40, color: [ { - field: '{{namespace[credset]}}contrib_fraction', + field: 'credset:contrib_fraction', scale_function: 'if', parameters: { field_value: 0, @@ -395,7 +377,7 @@

Source: ext/lz-credible-sets.js

}, { scale_function: 'interpolate', - field: '{{namespace[credset]}}contrib_fraction', + field: 'credset:contrib_fraction', parameters: { breaks: [0, 1], values: ['#fafe87', '#9c0000'], @@ -447,11 +429,11 @@

Source: ext/lz-credible-sets.js

responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, - toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association', { unnamespaced: true }), + toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'), panels: [ - LocusZoom.Layouts.get('panel', 'association_credible_set', { unnamespaced: true }), - LocusZoom.Layouts.get('panel', 'annotation_credible_set', { unnamespaced: true }), - LocusZoom.Layouts.get('panel', 'genes', { unnamespaced: true }), + LocusZoom.Layouts.get('panel', 'association_credible_set'), + LocusZoom.Layouts.get('panel', 'annotation_credible_set'), + LocusZoom.Layouts.get('panel', 'genes'), ], }; LocusZoom.Layouts.add('plot', 'association_credible_set', association_credible_set_plot); @@ -476,7 +458,7 @@

Source: ext/lz-credible-sets.js


diff --git a/docs/api/ext_lz-dynamic-urls.js.html b/docs/api/ext_lz-dynamic-urls.js.html index dd5bda47..5d8dcbf9 100644 --- a/docs/api/ext_lz-dynamic-urls.js.html +++ b/docs/api/ext_lz-dynamic-urls.js.html @@ -242,7 +242,7 @@

Source: ext/lz-dynamic-urls.js


diff --git a/docs/api/ext_lz-forest-track.js.html b/docs/api/ext_lz-forest-track.js.html index ecc5c180..bf1ca695 100644 --- a/docs/api/ext_lz-forest-track.js.html +++ b/docs/api/ext_lz-forest-track.js.html @@ -127,9 +127,9 @@

Source: ext/lz-forest-track.js

const y_scale = `y${this.layout.y_axis.axis}_scale`; // Generate confidence interval paths if fields are defined - if (this.layout.confidence_intervals - && this.layout.fields.includes(this.layout.confidence_intervals.start_field) - && this.layout.fields.includes(this.layout.confidence_intervals.end_field)) { + if (this.layout.confidence_intervals && + this.layout.confidence_intervals.start_field && + this.layout.confidence_intervals.end_field) { // Generate a selection for all forest plot confidence intervals const ci_selection = this.svg.group .selectAll('rect.lz-data_layer-forest.lz-data_layer-forest-ci') @@ -149,8 +149,10 @@

Source: ext/lz-forest-track.js

return `translate(${x}, ${y})`; }; const ci_width = (d) => { - return this.parent[x_scale](d[this.layout.confidence_intervals.end_field]) - - this.parent[x_scale](d[this.layout.confidence_intervals.start_field]); + const {start_field, end_field} = this.layout.confidence_intervals; + const scale = this.parent[x_scale]; + const result = scale(d[end_field]) - scale(d[start_field]); + return Math.max(result, 1); }; const ci_height = 1; // Create confidence interval rect elements @@ -161,7 +163,7 @@

Source: ext/lz-forest-track.js

.attr('transform', `translate(0, ${isNaN(this.parent.layout.height) ? 0 : this.parent.layout.height})`) .merge(ci_selection) .attr('transform', ci_transform) - .attr('width', ci_width) + .attr('width', ci_width) // Math.max(ci_width, 1)) .attr('height', ci_height); // Remove old elements as needed @@ -238,12 +240,10 @@

Source: ext/lz-forest-track.js

class CategoryForest extends Forest { _getDataExtent(data, axis_config) { // In a forest plot, the data range is determined by *three* fields (beta + CI start/end) - const ci_config = this.layout.confidence_intervals; - if (ci_config - && this.layout.fields.includes(ci_config.start_field) - && this.layout.fields.includes(ci_config.end_field)) { - const min = (d) => +d[ci_config.start_field]; - const max = (d) => +d[ci_config.end_field]; + const { confidence_intervals } = this.layout; + if (confidence_intervals && confidence_intervals.start_field && confidence_intervals.end_field) { + const min = (d) => +d[confidence_intervals.start_field]; + const max = (d) => +d[confidence_intervals.end_field]; return [d3.min(data, min), d3.max(data, max)]; } @@ -311,7 +311,7 @@

Source: ext/lz-forest-track.js


diff --git a/docs/api/ext_lz-intervals-enrichment.js.html b/docs/api/ext_lz-intervals-enrichment.js.html index 5bcbc09e..3197fee4 100644 --- a/docs/api/ext_lz-intervals-enrichment.js.html +++ b/docs/api/ext_lz-intervals-enrichment.js.html @@ -185,10 +185,10 @@

Source: ext/lz-intervals-enrichment.js

closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: `<b>Tissue</b>: {{{{namespace[intervals]}}tissueId|htmlescape}}<br> - <b>Range</b>: {{{{namespace[intervals]}}chromosome|htmlescape}}: {{{{namespace[intervals]}}start|htmlescape}}-{{{{namespace[intervals]}}end|htmlescape}}<br> - <b>-log10 p</b>: {{{{namespace[intervals]}}pValue|neglog10|scinotation|htmlescape}}<br> - <b>Enrichment (n-fold)</b>: {{{{namespace[intervals]}}fold|scinotation|htmlescape}}`, + html: `<b>Tissue</b>: {{intervals:tissueId|htmlescape}}<br> + <b>Range</b>: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}<br> + <b>-log10 p</b>: {{intervals:pValue|neglog10|scinotation|htmlescape}}<br> + <b>Enrichment (n-fold)</b>: {{intervals:fold|scinotation|htmlescape}}`, }; /** @@ -200,23 +200,22 @@

Source: ext/lz-intervals-enrichment.js

* @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions */ const intervals_layer_layout = { - namespace: { 'intervals': 'intervals' }, id: 'intervals_enrichment', type: 'intervals_enrichment', tag: 'intervals_enrichment', - match: { send: '{{namespace[intervals]}}tissueId' }, - fields: ['{{namespace[intervals]}}chromosome', '{{namespace[intervals]}}start', '{{namespace[intervals]}}end', '{{namespace[intervals]}}pValue', '{{namespace[intervals]}}fold', '{{namespace[intervals]}}tissueId', '{{namespace[intervals]}}ancestry'], - id_field: '{{namespace[intervals]}}start', // not a good ID field for overlapping intervals - start_field: '{{namespace[intervals]}}start', - end_field: '{{namespace[intervals]}}end', + namespace: { 'intervals': 'intervals' }, + match: { send: 'intervals:tissueId' }, + id_field: 'intervals:start', // not a good ID field for overlapping intervals + start_field: 'intervals:start', + end_field: 'intervals:end', filters: [ - { field: '{{namespace[intervals]}}ancestry', operator: '=', value: 'EU' }, - { field: '{{namespace[intervals]}}pValue', operator: '<=', value: 0.05 }, - { field: '{{namespace[intervals]}}fold', operator: '>', value: 2.0 }, + { field: 'intervals:ancestry', operator: '=', value: 'EU' }, + { field: 'intervals:pValue', operator: '<=', value: 0.05 }, + { field: 'intervals:fold', operator: '>', value: 2.0 }, ], y_axis: { axis: 1, - field: '{{namespace[intervals]}}fold', // is this used for other than extent generation? + field: 'intervals:fold', // is this used for other than extent generation? floor: 0, upper_buffer: 0.10, min_extent: [0, 10], @@ -224,7 +223,7 @@

Source: ext/lz-intervals-enrichment.js

fill_opacity: 0.5, // Many intervals overlap: show all, even if the ones below can't be clicked color: [ { - field: '{{namespace[intervals]}}tissueId', + field: 'intervals:tissueId', scale_function: 'stable_choice', parameters: { values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], @@ -255,19 +254,18 @@

Source: ext/lz-intervals-enrichment.js

id: 'interval_matches', type: 'highlight_regions', namespace: { intervals: 'intervals' }, - match: { receive: '{{namespace[intervals]}}tissueId' }, - fields: ['{{namespace[intervals]}}start', '{{namespace[intervals]}}end', '{{namespace[intervals]}}tissueId', '{{namespace[intervals]}}ancestry', '{{namespace[intervals]}}pValue', '{{namespace[intervals]}}fold'], - start_field: '{{namespace[intervals]}}start', - end_field: '{{namespace[intervals]}}end', - merge_field: '{{namespace[intervals]}}tissueId', + match: { receive: 'intervals:tissueId' }, + start_field: 'intervals:start', + end_field: 'intervals:end', + merge_field: 'intervals:tissueId', filters: [ { field: 'lz_is_match', operator: '=', value: true }, - { field: '{{namespace[intervals]}}ancestry', operator: '=', value: 'EU' }, - { field: '{{namespace[intervals]}}pValue', operator: '<=', value: 0.05 }, - { field: '{{namespace[intervals]}}fold', operator: '>', value: 2.0 }, + { field: 'intervals:ancestry', operator: '=', value: 'EU' }, + { field: 'intervals:pValue', operator: '<=', value: 0.05 }, + { field: 'intervals:fold', operator: '>', value: 2.0 }, ], color: [{ - field: '{{namespace[intervals]}}tissueId', + field: 'intervals:tissueId', scale_function: 'stable_choice', parameters: { values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], @@ -326,10 +324,10 @@

Source: ext/lz-intervals-enrichment.js

responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, - toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association', { unnamespaced: true }), + toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'), panels: [ function () { - const base = LocusZoom.Layouts.get('panel', 'association', { unnamespaced: true }); + const base = LocusZoom.Layouts.get('panel', 'association'); base.data_layers.unshift(intervals_highlight_layout); return base; }(), @@ -364,7 +362,7 @@

Source: ext/lz-intervals-enrichment.js


diff --git a/docs/api/ext_lz-intervals-track.js.html b/docs/api/ext_lz-intervals-track.js.html index 9e3df01a..ecc5e0ae 100644 --- a/docs/api/ext_lz-intervals-track.js.html +++ b/docs/api/ext_lz-intervals-track.js.html @@ -36,8 +36,10 @@

Source: ext/lz-intervals-track.js

* * {@link module:LocusZoom_ScaleFunctions~to_rgb} * * {@link module:LocusZoom_DataLayers~intervals} * * {@link module:LocusZoom_Layouts~standard_intervals} + * * {@link module:LocusZoom_Layouts~bed_intervals_layer} * * {@link module:LocusZoom_Layouts~intervals_layer} * * {@link module:LocusZoom_Layouts~intervals} + * * {@link module:LocusZoom_Layouts~bed_intervals} * * {@link module:LocusZoom_Layouts~interval_association} * * ### Loading and usage @@ -71,7 +73,7 @@

Source: ext/lz-intervals-track.js

function install (LocusZoom) { - const BaseApiAdapter = LocusZoom.Adapters.get('BaseApiAdapter'); + const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter'); const _Button = LocusZoom.Widgets.get('_Button'); const _BaseWidget = LocusZoom.Widgets.get('BaseWidget'); @@ -79,15 +81,17 @@

Source: ext/lz-intervals-track.js

* (**extension**) Retrieve Interval Annotation Data (e.g. BED Tracks), as fetched from the LocusZoom API server (or compatible) * @public * @alias module:LocusZoom_Adapters~IntervalLZ - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseUMAdapter * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions * @param {number} config.params.source The numeric ID for a specific dataset as assigned by the API server */ - class IntervalLZ extends BaseApiAdapter { - getURL(state, chain, fields) { - const source = chain.header.bedtracksource || this.params.source; - const query = `?filter=id in ${source} and chromosome eq '${state.chr}' and start le ${state.end} and end ge ${state.start}`; - return `${this.url}${query}`; + class IntervalLZ extends BaseUMAdapter { + _getURL(request_options) { + const source = this._config.source; + const query = `?filter=id in ${source} and chromosome eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}`; + + const base = super._getURL(request_options); + return `${base}${query}`; } } @@ -457,7 +461,7 @@

Source: ext/lz-intervals-track.js

.attr('x', 0) .attr('y', (d) => (d * height)) .attr('width', this.parent.layout.cliparea.width) - .attr('height', height - this.layout.track_vertical_spacing); + .attr('height', Math.max(height - this.layout.track_vertical_spacing, 1)); } status_nodes.exit() .remove(); @@ -472,7 +476,7 @@

Source: ext/lz-intervals-track.js

.attr('id', (d) => this.getElementId(d)) .attr('x', (d) => d[XCS]) .attr('y', (d) => d[YCS]) - .attr('width', (d) => d[XCE] - d[XCS]) + .attr('width', (d) => Math.max(d[XCE] - d[XCS], 1)) .attr('height', this.layout.track_height) .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i)) .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i)); @@ -569,10 +573,10 @@

Source: ext/lz-intervals-track.js

// Choose an appropriate color scheme based on the number of items in the track, and whether or not we are // using explicitly provided itemRgb information _makeColorScheme(category_info) { - // If at least one element has an explicit itemRgb, assume the entire dataset has colors + // If at least one element has an explicit itemRgb, assume the entire dataset has colors. BED intervals require rgb triplets,so assume that colors will always be "r,g,b" format. const has_explicit_colors = category_info.find((item) => item[2]); if (has_explicit_colors) { - return category_info.map((item) => item[2]); + return category_info.map((item) => to_rgb({}, item[2])); } // Use a set of color schemes for common 15, 18, or 25 state models, as specified from: @@ -629,16 +633,17 @@

Source: ext/lz-intervals-track.js

* @see {@link module:ext/lz-intervals-track} for required extension and installation instructions */ const intervals_tooltip_layout = { - namespace: { 'intervals': 'intervals' }, closable: false, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: '{{{{namespace[intervals]}}state_name|htmlescape}}<br>{{{{namespace[intervals]}}start|htmlescape}}-{{{{namespace[intervals]}}end|htmlescape}}', + html: '{{intervals:state_name|htmlescape}}<br>{{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}', }; /** * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for chromHMM output, - * in which various states are assigned numeric state IDs and (<= as many) text state names + * in which various states are assigned numeric state IDs and (<= as many) text state names. + * + * This layout is deprecated; most usages would be better served by the bed_intervals_layer layout instead. * @alias module:LocusZoom_Layouts~intervals_layer * @type data_layer * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions @@ -648,23 +653,22 @@

Source: ext/lz-intervals-track.js

id: 'intervals', type: 'intervals', tag: 'intervals', - fields: ['{{namespace[intervals]}}start', '{{namespace[intervals]}}end', '{{namespace[intervals]}}state_id', '{{namespace[intervals]}}state_name', '{{namespace[intervals]}}itemRgb'], - id_field: '{{namespace[intervals]}}start', // FIXME: This is not a good D3 "are these datums redundant" ID for datasets with multiple intervals heavily overlapping - start_field: '{{namespace[intervals]}}start', - end_field: '{{namespace[intervals]}}end', - track_split_field: '{{namespace[intervals]}}state_name', - track_label_field: '{{namespace[intervals]}}state_name', + id_field: '{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}', + start_field: 'intervals:start', + end_field: 'intervals:end', + track_split_field: 'intervals:state_name', + track_label_field: 'intervals:state_name', split_tracks: false, always_hide_legend: true, color: [ { // If present, an explicit color field will override any other option (and be used to auto-generate legend) - field: '{{namespace[intervals]}}itemRgb', + field: 'intervals:itemRgb', scale_function: 'to_rgb', }, { // TODO: Consider changing this to stable_choice in the future, for more stable coloring - field: '{{namespace[intervals]}}state_name', + field: 'intervals:state_name', scale_function: 'categorical_bin', parameters: { // Placeholder. Empty categories and values will automatically be filled in when new data loads. @@ -692,8 +696,50 @@

Source: ext/lz-intervals-track.js

tooltip: intervals_tooltip_layout, }; + /** - * (**extension**) A panel containing an intervals data layer, eg for BED tracks + * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for standard BED3+ files and the field names emitted by the LzParsers extension. + * @alias module:LocusZoom_Layouts~bed_intervals_layer + * @type data_layer + * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions + */ + const bed_intervals_layer_layout = LocusZoom.Layouts.merge({ + id_field: '{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}', + start_field: 'intervals:chromStart', + end_field: 'intervals:chromEnd', + track_split_field: 'intervals:name', + track_label_field: 'intervals:name', + split_tracks: true, + always_hide_legend: false, + color: [ + { + // If present, an explicit color field will override any other option (and be used to auto-generate legend) + field: 'intervals:itemRgb', + scale_function: 'to_rgb', + }, + { + // TODO: Consider changing this to stable_choice in the future, for more stable coloring + field: 'intervals:name', + scale_function: 'categorical_bin', + parameters: { + // Placeholder. Empty categories and values will automatically be filled in when new data loads. + categories: [], + values: [], + null_value: '#B8B8B8', + }, + }, + ], + tooltip: LocusZoom.Layouts.merge({ + html: `<strong>Group: </strong>{{intervals:name|htmlescape}}<br> +<strong>Region: </strong>{{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}} +{{#if intervals:score}}<br> +<strong>Score:</strong> {{intervals:score|htmlescape}}{{/if}}`, + }, intervals_tooltip_layout), + }, intervals_layer_layout); + + + /** + * (**extension**) A panel containing an intervals data layer, eg for BED tracks. This is a legacy layout whose field names were specific to one partner site. * @alias module:LocusZoom_Layouts~intervals * @type panel * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions @@ -705,7 +751,7 @@

Source: ext/lz-intervals-track.js

height: 50, margin: { top: 25, right: 150, bottom: 5, left: 50 }, toolbar: (function () { - const l = LocusZoom.Layouts.get('toolbar', 'standard_panel', { unnamespaced: true }); + const l = LocusZoom.Layouts.get('toolbar', 'standard_panel'); l.widgets.push({ type: 'toggle_split_tracks', data_layer_id: 'intervals', @@ -728,9 +774,22 @@

Source: ext/lz-intervals-track.js

data_layers: [intervals_layer_layout], }; + /** + * (**extension**) A panel containing an intervals data layer, eg for BED tracks. These field names match those returned by the LzParsers extension. + * @alias module:LocusZoom_Layouts~bed_intervals + * @type panel + * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions + */ + const bed_intervals_panel_layout = LocusZoom.Layouts.merge({ + // Normal BED tracks show the panel legend in collapsed mode! + min_height: 120, + height: 120, + data_layers: [bed_intervals_layer_layout], + }, intervals_panel_layout); + /** * (**extension**) A plot layout that shows association summary statistics, genes, and interval data. This example assumes - * chromHMM data. (see panel layout) + * chromHMM data. (see panel layout) Few people will use the full intervals plot layout directly outside of an example. * @alias module:LocusZoom_Layouts~interval_association * @type plot * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions @@ -741,10 +800,10 @@

Source: ext/lz-intervals-track.js

responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, - toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association', { unnamespaced: true }), + toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'), panels: [ LocusZoom.Layouts.get('panel', 'association'), - LocusZoom.Layouts.merge({ unnamespaced: true, min_height: 120, height: 120 }, intervals_panel_layout), + LocusZoom.Layouts.merge({ min_height: 120, height: 120 }, intervals_panel_layout), LocusZoom.Layouts.get('panel', 'genes'), ], }; @@ -754,7 +813,9 @@

Source: ext/lz-intervals-track.js

LocusZoom.Layouts.add('tooltip', 'standard_intervals', intervals_tooltip_layout); LocusZoom.Layouts.add('data_layer', 'intervals', intervals_layer_layout); + LocusZoom.Layouts.add('data_layer', 'bed_intervals', bed_intervals_layer_layout); LocusZoom.Layouts.add('panel', 'intervals', intervals_panel_layout); + LocusZoom.Layouts.add('panel', 'bed_intervals', bed_intervals_panel_layout); LocusZoom.Layouts.add('plot', 'interval_association', intervals_plot_layout); LocusZoom.ScaleFunctions.add('to_rgb', to_rgb); @@ -780,7 +841,7 @@

Source: ext/lz-intervals-track.js


diff --git a/docs/api/ext_lz-parsers_bed.js.html b/docs/api/ext_lz-parsers_bed.js.html new file mode 100644 index 00000000..3b4f8632 --- /dev/null +++ b/docs/api/ext_lz-parsers_bed.js.html @@ -0,0 +1,165 @@ + + + + + JSDoc: Source: ext/lz-parsers/bed.js + + + + + + + + + + +
+ +

Source: ext/lz-parsers/bed.js

+ + + + + + +
+
+
/**
+ * Parse BED-family files, which have 3-12 columns
+ *   https://genome.ucsc.edu/FAQ/FAQformat.html#format1
+ */
+
+import {normalizeChr} from './utils';
+
+/**
+ * @private
+ */
+function _bedMissing(value) {
+    // BED files specify . as the missing/ null value character
+    if (value === null || value === undefined || value === '.') {
+        return null;
+    }
+    return value;
+}
+
+/**
+ * @private
+ */
+function _hasNum(value) {
+    // Return a number, or null if value marked as missing
+    value = _bedMissing(value);
+    return value ? +value : null;
+}
+
+/**
+ * Parse a BED file, according to the widely used UCSC (quasi-)specification
+ *
+ * NOTE: This original version is aimed at tabix region queries, and carries an implicit assumption that data is the
+ *  only thing that will be parsed. It makes no attempt to identify or handle header rows / metadata fields.
+ *
+ * @function
+ * @alias module:ext/lz-parsers~makeBed12Parser
+ * @param {object} options
+ * @param {Boolean} options.normalize Whether to normalize the output to the format expected by LocusZoom (eg type coercion
+ *  for numbers, removing chr chromosome prefixes, and using 1-based and inclusive coordinates instead of 0-based disjoint intervals)
+ * @return function A configured parser function that runs on one line of text from an input file
+ */
+function makeBed12Parser({normalize = true} = {}) {
+    /*
+     * @param {String} line The line of text to be parsed
+     */
+    return (line) => {
+        const tokens = line.trim().split('\t');
+        // The BED file format has 12 standardized columns. 3 are required and 9 are optional. At present, we will not
+        //  attempt to parse any remaining tokens, or nonstandard files that reuse columns with a different meaning.
+        //   https://en.wikipedia.org/wiki/BED_(file_format)
+        let [
+            chrom,
+            chromStart,
+            chromEnd,
+            name,
+            score,
+            strand,
+            thickStart,
+            thickEnd,
+            itemRgb,
+            blockCount,
+            blockSizes,
+            blockStarts,
+        ] = tokens;
+
+        if (!(chrom && chromStart && chromEnd)) {
+            throw new Error('Sample data must provide all required BED columns');
+        }
+
+        strand = _bedMissing(strand);
+
+        if (normalize) {
+            // Mandatory fields
+            chrom = normalizeChr(chrom);
+            chromStart = +chromStart + 1;  // BED is 0 based start, but LZ plots start at 1
+            chromEnd = +chromEnd; // 0-based positions, intervals exclude end position
+
+            // Optional fields, require checking for blanks
+            score = _hasNum(score);
+            thickStart = _hasNum(thickStart);
+            thickEnd = _hasNum(thickEnd);
+
+            itemRgb = _bedMissing(itemRgb);
+
+            // LocusZoom doesn't use these fields for rendering. Parsing below is theoretical/best-effort.
+            blockCount = _hasNum(blockCount);
+
+            blockSizes = _bedMissing(blockSizes);
+            blockSizes = !blockSizes ? null : blockSizes.replace(/,$/, '').split(',').map((value) => +value); // Comma separated list of sizes -> array of integers
+
+            blockStarts = _bedMissing(blockStarts);
+            blockStarts = !blockStarts ? null : blockStarts.replace(/,$/, '').split(',').map((value) => +value + 1); // Comma separated list of sizes -> array of integers (start positions)
+
+            if (blockSizes && blockStarts && blockCount && (blockSizes.length !== blockCount || blockStarts.length !== blockCount)) {
+                throw new Error('Block size and start information should provide the same number of items as in blockCount');
+            }
+        }
+        return {
+            chrom,
+            chromStart,
+            chromEnd,
+            name,
+            score,
+            strand,
+            thickStart,
+            thickEnd,
+            itemRgb,
+            blockCount,
+            blockSizes,
+            blockStarts,
+        };
+    };
+}
+
+export { makeBed12Parser };
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/api/ext_lz-parsers_gwas_parsers.js.html b/docs/api/ext_lz-parsers_gwas_parsers.js.html new file mode 100644 index 00000000..074b7d2e --- /dev/null +++ b/docs/api/ext_lz-parsers_gwas_parsers.js.html @@ -0,0 +1,228 @@ + + + + + JSDoc: Source: ext/lz-parsers/gwas/parsers.js + + + + + + + + + + +
+ +

Source: ext/lz-parsers/gwas/parsers.js

+ + + + + + +
+
+
import {parseMarker} from '../../../helpers/parse';
+
+import {
+    MISSING_VALUES,
+    has,
+    parseAlleleFrequency,
+    parsePvalToLog, normalizeChr,
+} from '../utils';
+
+
+/**
+ * Specify how to parse a GWAS file, given certain column information.
+ * Outputs an object with fields in portal API format.
+ *
+ * All column options must be provided as 1-indexed column IDs (human-friendly argument values)
+ * @function
+ * @alias module:ext/lz-parsers~makeGWASParser
+ * @param options
+ * @param [options.marker_col] A single identifier that specifies all of chrom, pos, ref, and alt as a single string field. Eg 1:23_A/C
+ * @param [options.chrom_col] Chromosome
+ * @param [options.pos_col] Position
+ * @param [options.ref_col] Reference allele (relative to human reference genome, eg GRCh37 or 38).
+ * @param [options.alt_col] Alt allele. Some programs specify generic A1/A2 instead; it is the job of the user to identify which columns of this GWAS are ref and alt.
+ * @param [options.rsid_col] rsID
+ * @param options.pvalue_col p-value (or -log10p)
+ * @param [options.beta_col]
+ * @param [options.stderr_beta_col]
+ * @param [options.allele_freq_col] Specify allele frequencies directly
+ * @param [options.allele_count_col] Specify allele frequencies in terms of count and n_samples
+ * @param [options.n_samples_col]
+ * @param [options.is_alt_effect=true] Some programs specify beta and frequency information in terms of ref, others alt. Identify effect allele to orient values to the correct allele.
+ * @param [options.is_neg_log_pvalue=false]
+ * @param [options.delimiter='\t'] Since this parser is usually used with tabix data, this is rarely changed (tabix does not accept other delimiters)
+ * @return {function(string)} A parser function that can be called on each line of text with the provided options
+ */
+function makeGWASParser(
+    {
+        // Required fields
+        marker_col, // Identify the variant: marker OR chrom/pos/ref/alt
+        chrom_col,
+        pos_col,
+        ref_col,
+        alt_col,
+        pvalue_col, // pvalue (or log_pvalue; see options below)
+        // Optional fields
+        is_neg_log_pvalue = false,
+        rsid_col,
+        beta_col,
+        stderr_beta_col,
+        allele_freq_col, // Frequency: given directly, OR in terms of counts
+        allele_count_col,
+        n_samples_col,
+        is_alt_effect = true, // whether effect allele is oriented towards alt. We don't support files like METAL, where ref/alt may switch places per line of the file
+        delimiter = '\t',
+    }
+) {
+    // Column IDs should be 1-indexed (human friendly)
+    if (has(marker_col) && has(chrom_col) && has(pos_col)) {
+        throw new Error('Must specify either marker OR chr + pos');
+    }
+    if (!(has(marker_col) || (has(chrom_col) && has(pos_col)))) {
+        throw new Error('Must specify how to locate marker');
+    }
+
+    if (has(allele_count_col) && has(allele_freq_col)) {
+        throw new Error('Allele count and frequency options are mutually exclusive');
+    }
+    if (has(allele_count_col) && !has(n_samples_col)) {
+        throw new Error('To calculate allele frequency from counts, you must also provide n_samples');
+    }
+
+
+    return (line) => {
+        const fields = line.split(delimiter);
+        let chr;
+        let pos;
+        let ref;
+        let alt;
+        let rsid = null;
+
+        let freq;
+        let beta = null;
+        let stderr_beta = null;
+        let alt_allele_freq = null;
+        let allele_count;
+        let n_samples;
+
+        if (has(marker_col)) {
+            [chr, pos, ref, alt] = parseMarker(fields[marker_col - 1], false);
+        } else if (has(chrom_col) && has(pos_col)) {
+            chr = fields[chrom_col - 1];
+            pos = fields[pos_col - 1];
+        } else {
+            throw new Error('Must specify all fields required to identify the variant');
+        }
+
+        chr = normalizeChr(chr);
+        if (chr.startsWith('RS')) {
+            throw new Error(`Invalid chromosome specified: value "${chr}" is an rsID`);
+        }
+
+        if (has(ref_col)) {
+            ref = fields[ref_col - 1];
+        }
+
+        if (has(alt_col)) {
+            alt = fields[alt_col - 1];
+        }
+
+        if (has(rsid_col)) {
+            rsid = fields[rsid_col - 1];
+        }
+
+        if (MISSING_VALUES.has(ref)) {
+            ref = null;
+        }
+        if (MISSING_VALUES.has(alt)) {
+            alt = null;
+        }
+
+        if (MISSING_VALUES.has(rsid)) {
+            rsid = null;
+        } else if (rsid) {
+            rsid = rsid.toLowerCase();
+            if (!rsid.startsWith('rs')) {
+                rsid = `rs${rsid}`;
+            }
+        }
+
+        const log_pval = parsePvalToLog(fields[pvalue_col - 1], is_neg_log_pvalue);
+        ref = ref || null;
+        alt = alt || null;
+
+        if (has(allele_freq_col)) {
+            freq = fields[allele_freq_col - 1];
+        }
+        if (has(allele_count_col)) {
+            allele_count = fields[allele_count_col - 1];
+            n_samples = fields[n_samples_col - 1];
+        }
+
+        if (has(beta_col)) {
+            beta = fields[beta_col - 1];
+            beta = MISSING_VALUES.has(beta) ? null : (+beta);
+        }
+
+        if (has(stderr_beta_col)) {
+            stderr_beta = fields[stderr_beta_col - 1];
+            stderr_beta = MISSING_VALUES.has(stderr_beta) ? null : (+stderr_beta);
+        }
+
+        if (allele_freq_col || allele_count_col) {
+            alt_allele_freq = parseAlleleFrequency({
+                freq,
+                allele_count,
+                n_samples,
+                is_alt_effect,
+            });
+        }
+        const ref_alt = (ref && alt) ? `_${ref}/${alt}` : '';
+        return {
+            chromosome: chr,
+            position: +pos,
+            ref_allele: ref ? ref.toUpperCase() : null,
+            alt_allele: alt ? alt.toUpperCase() : null,
+            variant: `${chr}:${pos}${ref_alt}`,
+            rsid,
+            log_pvalue: log_pval,
+            beta,
+            stderr_beta,
+            alt_allele_freq,
+        };
+    };
+}
+
+
+export { makeGWASParser };
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/api/ext_lz-parsers_index.js.html b/docs/api/ext_lz-parsers_index.js.html new file mode 100644 index 00000000..5ca651d7 --- /dev/null +++ b/docs/api/ext_lz-parsers_index.js.html @@ -0,0 +1,152 @@ + + + + + JSDoc: Source: ext/lz-parsers/index.js + + + + + + + + + + +
+ +

Source: ext/lz-parsers/index.js

+ + + + + + +
+
+
/**
+ * Optional LocusZoom extension: must be included separately, and after LocusZoom has been loaded
+ *
+ * This plugin exports helper function, as well as a few optional extra helpers for rendering the plot. The GWAS parsers can be used without registering the plugin.
+ *
+ * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):
+ * ```
+ * <script src="https://cdn.jsdelivr.net/npm/locuszoom@INSERT_VERSION_HERE/dist/ext/lz-parsers.min.js" type="application/javascript"></script>
+ * ```
+ *
+ * To use with ES6 modules, import the helper functions and use them with your layout:
+ *
+ * ```
+ * import { install, makeGWASParser, makeBed12Parser, makePlinkLDParser } from 'locuszoom/esm/ext/lz-parsers';
+ * LocusZoom.use(install);
+ * ```
+ *
+ * ### Features provided
+ * * {@link module:LocusZoom_Adapters~UserTabixLD} (if the {@link module:ext/lz-tabix-source} extension is loaded first)
+ *
+ * @module ext/lz-parsers
+ */
+
+import { makeBed12Parser } from './bed';
+import { makeGWASParser } from './gwas/parsers';
+import { guessGWAS } from './gwas/sniffers';
+import { makePlinkLdParser } from './ld';
+
+
+// Most of this plugin consists of standalone functions. But we can add a few simple custom classes to the registry that help to use parsed output
+function install(LocusZoom) {
+    if (LocusZoom.Adapters.has('TabixUrlSource')) {
+        // Custom Tabix adapter depends on another extension being loaded first
+        const TabixUrlSource = LocusZoom.Adapters.get('TabixUrlSource');
+        const LDServer = LocusZoom.Adapters.get('LDServer');
+
+        /**
+         * Load user-provided LD from a tabix file, and filter the returned set of records based on a reference variant. (will attempt to choose a reference variant based on the most significant association variant, if no state.ldrefvar is specified)
+         * @public
+         * @alias module:LocusZoom_Adapters~UserTabixLD
+         * @extends module:LocusZoom_Adapters~TabixUrlSource
+         * @see {@link module:ext/lz-tabix-source} for required extension and installation instructions
+         * @see {@link module:ext/lz-parsers} for required extension and installation instructions
+         */
+        class UserTabixLD extends TabixUrlSource {
+            constructor(config) {
+                if (!config.limit_fields) {
+                    config.limit_fields = ['variant2', 'position2', 'correlation'];
+                }
+                super(config);
+            }
+
+            _buildRequestOptions(state, assoc_data) {
+                if (!assoc_data) {
+                    throw new Error('LD request must depend on association data');
+                }
+                // If no state refvar is provided, find the most significant variant in any provided assoc data.
+                //   Assumes that assoc satisfies the "assoc" fields contract, eg has fields variant and log_pvalue
+                const base = super._buildRequestOptions(...arguments);
+                if (!assoc_data.length) {
+                    base._skip_request = true;
+                    return base;
+                }
+
+                // NOTE: Reuses a method from another adapter to mix in functionality
+                base.ld_refvar = LDServer.prototype.__find_ld_refvar(state, assoc_data);
+                return base;
+            }
+
+            _performRequest(options) {
+                // Skip request if this one depends on other data, and we are in a region with no data
+                if (options._skip_request) {
+                    return Promise.resolve([]);
+                }
+                return super._performRequest(options);
+            }
+
+            _annotateRecords(records, options) {
+                // A single PLINK LD file could contain several reference variants (SNP_A) in the same region.
+                //   Only show LD relative to the user-selected refvar in this plot.
+                return records.filter((item) => item['variant1'] === options.ld_refvar);
+            }
+        }
+
+
+        LocusZoom.Adapters.add('UserTabixLD', UserTabixLD);
+    }
+}
+
+if (typeof LocusZoom !== 'undefined') {
+    // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()
+    // eslint-disable-next-line no-undef
+    LocusZoom.use(install);
+}
+
+// Support UMD (single symbol export)
+const all = { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser };
+
+export default all;
+
+export { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser };
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/api/ext_lz-parsers_ld.js.html b/docs/api/ext_lz-parsers_ld.js.html new file mode 100644 index 00000000..f5e83b3a --- /dev/null +++ b/docs/api/ext_lz-parsers_ld.js.html @@ -0,0 +1,88 @@ + + + + + JSDoc: Source: ext/lz-parsers/ld.js + + + + + + + + + + +
+ +

Source: ext/lz-parsers/ld.js

+ + + + + + +
+
+
/**
+ * Parsers for custom user-specified LD
+ */
+
+import {normalizeChr} from './utils';
+import {normalizeMarker} from '../../helpers/parse';
+
+/**
+ * Parse the output of plink v1.9 R2 calculations relative to one (or a few) target SNPs.
+ * See: https://www.cog-genomics.org/plink/1.9/ld and
+ * reformatting commands at https://www.cog-genomics.org/plink/1.9/other
+ * @function
+ * @alias module:ext/lz-parsers~makePlinkLdParser
+ * @param {object} options
+ * @param {boolean} [options.normalize=true] Normalize fields to expected datatypes and format; if false, returns raw strings as given in the file
+ * @return {function} A configured parser function that runs on one line of text from an input file
+ */
+function makePlinkLdParser({normalize = true} = {}) {
+    return (line) => {
+        // Sample headers are below: SNP_A and SNP_B are based on ID column of the VCF
+        // CHR_A   BP_A    SNP_A   CHR_B   BP_B    SNP_B   R2
+        let [chromosome1, position1, variant1, chromosome2, position2, variant2, correlation] = line.trim().split('\t');
+        if (normalize) {
+            chromosome1 = normalizeChr(chromosome1);
+            chromosome2 = normalizeChr(chromosome2);
+            variant1 = normalizeMarker(variant1);
+            variant2 = normalizeMarker(variant2);
+            position1 = +position1;
+            position2 = +position2;
+            correlation = +correlation;
+        }
+
+        return {chromosome1, position1, variant1, chromosome2, position2, variant2, correlation};
+    };
+}
+
+export { makePlinkLdParser };
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/api/ext_lz-tabix-source.js.html b/docs/api/ext_lz-tabix-source.js.html index f575eebf..cbddba5a 100644 --- a/docs/api/ext_lz-tabix-source.js.html +++ b/docs/api/ext_lz-tabix-source.js.html @@ -37,7 +37,6 @@

Source: ext/lz-tabix-source.js

* The page must incorporate and load all libraries before this file can be used, including: * - Vendor assets * - LocusZoom - * - tabix-reader (available via NPM or a related CDN) * * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies): * ``` @@ -60,17 +59,17 @@

Source: ext/lz-tabix-source.js

* // Tabix performs region queries. If you are fetching interval data (one end outside the edge of the plot), then * // "overfetching" can help to ensure that data partially outside the view region is retrieved * // If you are fetching single-point data like association summary stats, then overfetching is unnecessary - * params: { overfetch: 0.25 } + * overfetch: 0.25 * }]); * ``` * * @module */ -import tabix from 'tabix-reader'; +import { urlReader } from 'tabix-reader'; function install(LocusZoom) { - const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter'); + const BaseLZAdapter = LocusZoom.Adapters.get('BaseLZAdapter'); /** * Loads data from a remote Tabix file (if the file host has been configured with proper @@ -80,32 +79,32 @@

Source: ext/lz-tabix-source.js

* * @alias module:LocusZoom_Adapters~TabixUrlSource * @see {@link module:ext/lz-tabix-source} for required extension and installation instructions - * @see module:LocusZoom_Adapters~BaseApiAdapter + * @see module:LocusZoom_Adapters~BaseLZAdapter * @param {function} config.parser_func A function that parses a single line of text and returns (usually) a * structured object of data fields * @param {string} config.url_data The URL for the bgzipped and tabix-indexed file * @param {string} [config.url_tbi] The URL for the tabix index. Defaults to `url_data` + '.tbi' - * @param {number} [config.params.overfetch = 0] Optionally fetch more data than is required to satisfy the + * @param {Promise<Reader>} [config.reader] The URL for tabix-reader instance that provides the data. Mutually exclusive with providing a URL. + * Most LocusZoom usages will not pass an external reader. This option exists for websites like LocalZoom that accept + * many file formats and want to perform input validation before creating the plot: "select parser options", etc. + * @param {number} [config.overfetch = 0] Optionally fetch more data than is required to satisfy the * region query. (specified as a fraction of the region size, 0-1). * Useful for sources where interesting features might lie near the edges of the plot, eg BED track intervals. */ - class TabixUrlSource extends BaseAdapter { - parseInit(init) { - if (!init.parser_func || !init.url_data) { + class TabixUrlSource extends BaseLZAdapter { + constructor(config) { + super(config); + if (!config.parser_func || !(config.url_data || config.reader)) { throw new Error('Tabix source is missing required configuration options'); } - this.parser = init.parser_func; - // TODO: In the future, accept a pre-configured reader instance (as an alternative to the URL). Most useful - // for UIs that want to validate the tabix file before adding it to the plot, like LocalZoom. - this.url_data = init.url_data; - this.url_tbi = init.url_tbi || `${this.url_data}.tbi`; + this.parser = config.parser_func; + this.url_data = config.url_data; + this.url_tbi = config.url_tbi || `${this.url_data}.tbi`; // In tabix mode, sometimes we want to fetch a slightly larger region than is displayed, in case a // feature is on the edge of what the tabix query would return. // Specify overfetch in units of % of total region size. ("fetch 10% extra before and after") - const params = init.params || {}; - this.params = params; - this._overfetch = params.overfetch || 0; + this._overfetch = config.overfetch || 0; if (this._overfetch < 0 || this._overfetch > 1) { throw new Error('Overfetch must be specified as a fraction (0-1) of the requested region size'); @@ -113,22 +112,26 @@

Source: ext/lz-tabix-source.js

// Assuming that the `tabix-reader` library has been loaded via a CDN, this will create the reader // Since fetching the index is a remote operation, all reader usages will be via an async interface. - this._reader_promise = tabix.urlReader(this.url_data, this.url_tbi).catch(function () { - throw new Error('Failed to create a tabix reader from the provided URL'); - }); + if (this.url_data) { + this._reader_promise = urlReader(this.url_data, this.url_tbi).catch(function () { + throw new Error('Failed to create a tabix reader from the provided URL'); + }); + } else { + this._reader_promise = Promise.resolve(config.reader); + } } - fetchRequest(state /*, chain, fields */) { + _performRequest(options) { return new Promise((resolve, reject) => { // Ensure that the reader is fully created (and index available), then make a query - const region_start = state.start; - const region_end = state.end; + const region_start = options.start; + const region_end = options.end; const extra_amount = this._overfetch * (region_end - region_start); - const start = state.start - extra_amount; - const end = state.end + extra_amount; + const start = options.start - extra_amount; + const end = options.end + extra_amount; this._reader_promise.then((reader) => { - reader.fetch(state.chr, start, end, function (data, err) { + reader.fetch(options.chr, start, end, function (data, err) { if (err) { reject(new Error('Could not read requested region. This may indicate an error with the .tbi index.')); } @@ -138,9 +141,9 @@

Source: ext/lz-tabix-source.js

}); } - normalizeResponse(data) { + _normalizeResponse(records) { // Parse the data from lines of text to objects - return data.map(this.parser); + return records.map(this.parser); } } @@ -166,7 +169,7 @@

Source: ext/lz-tabix-source.js


diff --git a/docs/api/ext_lz-widget-addons.js.html b/docs/api/ext_lz-widget-addons.js.html index 75ab7163..1f648975 100644 --- a/docs/api/ext_lz-widget-addons.js.html +++ b/docs/api/ext_lz-widget-addons.js.html @@ -254,7 +254,7 @@

Source: ext/lz-widget-addons.js

this.button.menu.setPopulate(() => { this.button.menu.inner_selector.html(''); const table = this.button.menu.inner_selector.append('table'); - this.parent_panel.data_layer_ids_by_z_index.slice().reverse().forEach((id, idx) => { + this.parent_panel._data_layer_ids_by_z_index.slice().reverse().forEach((id, idx) => { const data_layer = this.parent_panel.data_layers[id]; const name = (typeof data_layer.layout.name != 'string') ? data_layer.id : data_layer.layout.name; const row = table.append('tr'); @@ -265,7 +265,7 @@

Source: ext/lz-widget-addons.js

const status_idx = STATUS_ADJECTIVES.indexOf(status_adj); const status_verb = STATUS_VERBS[status_idx]; let html, onclick, highlight; - if (data_layer.global_statuses[status_adj]) { + if (data_layer._global_statuses[status_adj]) { html = STATUS_ANTIVERBS[status_idx]; onclick = `un${status_verb}AllElements`; highlight = '-highlighted'; @@ -285,7 +285,7 @@

Source: ext/lz-widget-addons.js

}); // Sort layer buttons const at_top = (idx === 0); - const at_bottom = (idx === (this.parent_panel.data_layer_ids_by_z_index.length - 1)); + const at_bottom = (idx === (this.parent_panel._data_layer_ids_by_z_index.length - 1)); const td = row.append('td'); td.append('a') .attr('class', `lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${at_bottom ? '-disabled' : ''}`) @@ -325,13 +325,13 @@

Source: ext/lz-widget-addons.js

} const covariates_model_tooltip = function () { - const covariates_model_association = LocusZoom.Layouts.get('tooltip', 'standard_association', { unnamespaced: true }); + const covariates_model_association = LocusZoom.Layouts.get('tooltip', 'standard_association'); covariates_model_association.html += '<a href="javascript:void(0);" onclick="LocusZoom.getToolTipPlot(this).CovariatesModel.add(LocusZoom.getToolTipData(this));">Condition on Variant</a><br>'; return covariates_model_association; }(); const covariates_model_plot = function () { - const covariates_model_plot_toolbar = LocusZoom.Layouts.get('toolbar', 'standard_association', { unnamespaced: true }); + const covariates_model_plot_toolbar = LocusZoom.Layouts.get('toolbar', 'standard_association'); covariates_model_plot_toolbar.widgets.push({ type: 'covariates_model', button_html: 'Model', @@ -366,7 +366,7 @@

Source: ext/lz-widget-addons.js


diff --git a/docs/api/global.html b/docs/api/global.html index a9d15703..272d1153 100644 --- a/docs/api/global.html +++ b/docs/api/global.html @@ -283,7 +283,7 @@
Properties
Source:
@@ -319,7 +319,7 @@
Properties
-

externalDataCallback(new_data)

+

externalDataCallback(new_data, plot)

@@ -384,6 +384,30 @@
Parameters:
+ + + + plot + + + + + +Object + + + + + + + + + +

A reference to the plot object. This can be useful for listeners that react to the +structure of the data, instead of just displaying something.

+ + + @@ -421,7 +445,7 @@
Parameters:
Source:
@@ -553,7 +577,7 @@
Parameters:
Source:
@@ -796,6 +820,230 @@
Properties:
+ + + + + + +

data_from_layer

+ + + + + + +
+

One particular data layer has completed a request for data. This event is primarily used internally by the subscribeToData function, and the syntax may change in the future.

+
+ + + + + + + + + + + + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
data + + +object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
layer + + +String + + + +

The fully qualified ID of the layer emitting this event

content + + +Array.<Object> + + + +

The data used to draw this layer: an array where each element represents one row/ datum +element. It reflects all namespaces and data operations used by that layer.

+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + +
See:
+
+ +
+ + + +
+ + + + + + + + + +
Listeners of This Event:
+ + + + + + + + + + + + + @@ -1056,7 +1304,7 @@

element_clicked

Source:
@@ -1206,7 +1454,7 @@
Properties:
Source:
@@ -1308,7 +1556,7 @@

layout_changed

Source:
@@ -1456,7 +1704,7 @@
Properties:
Source:
@@ -1757,7 +2005,7 @@
Properties:
Source:
@@ -2054,7 +2302,7 @@
Properties:
Source:
@@ -2110,7 +2358,7 @@
Listeners of This Event:

diff --git a/docs/api/helpers_common.js.html b/docs/api/helpers_common.js.html index 8d2d49be..e0a2516c 100644 --- a/docs/api/helpers_common.js.html +++ b/docs/api/helpers_common.js.html @@ -291,7 +291,7 @@

Source: helpers/common.js


diff --git a/docs/api/helpers_display.js.html b/docs/api/helpers_display.js.html index 7a4fb411..7bd735b1 100644 --- a/docs/api/helpers_display.js.html +++ b/docs/api/helpers_display.js.html @@ -188,7 +188,7 @@

Source: helpers/display.js

// `tokens` is like [token,...] // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'} const tokens = []; - const regex = /{{(?:(#if )?([A-Za-z0-9_:|]+)|(#else)|(\/if))}}/; + const regex = /{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/; while (html.length > 0) { const m = regex.exec(html); if (!m) { @@ -394,7 +394,7 @@

Source: helpers/display.js


diff --git a/docs/api/helpers_jsonpath.js.html b/docs/api/helpers_jsonpath.js.html index 77e4ec52..a6ad1ab5 100644 --- a/docs/api/helpers_jsonpath.js.html +++ b/docs/api/helpers_jsonpath.js.html @@ -264,7 +264,7 @@

Source: helpers/jsonpath.js


diff --git a/docs/api/helpers_layouts.js.html b/docs/api/helpers_layouts.js.html index 6ad78c30..4cbe143f 100644 --- a/docs/api/helpers_layouts.js.html +++ b/docs/api/helpers_layouts.js.html @@ -49,52 +49,38 @@

Source: helpers/layouts.js

}; /** - * Apply namespaces to layout, recursively + * Apply shared namespaces to a layout, recursively. + * + * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout. + * For that, a key would have to be added to `layout.namespace` directly. + * + * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy + * over keys that are relevant to that data layer. Eg, if overrides specifies a key called "red_herring", + * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`. + * + * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify + * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself. * @private */ -function applyNamespaces(element, namespace, default_namespace) { - if (namespace) { - if (typeof namespace == 'string') { - namespace = { default: namespace }; - } - } else { - namespace = { default: '' }; +function applyNamespaces(layout, shared_namespaces) { + shared_namespaces = shared_namespaces || {}; + if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') { + throw new Error('Layout and shared namespaces must be provided as objects'); } - if (typeof element == 'string') { - const re = /\{\{namespace(\[[A-Za-z_0-9]+\]|)\}\}/g; - let match, base, key, resolved_namespace; - const replace = []; - while ((match = re.exec(element)) !== null) { - base = match[0]; - key = match[1].length ? match[1].replace(/(\[|\])/g, '') : null; - resolved_namespace = default_namespace; - if (namespace != null && typeof namespace == 'object' && typeof namespace[key] != 'undefined') { - resolved_namespace = namespace[key] + (namespace[key].length ? ':' : ''); - } - replace.push({ base: base, namespace: resolved_namespace }); - } - for (let r in replace) { - element = element.replace(replace[r].base, replace[r].namespace); - } - } else if (typeof element == 'object' && element != null) { - if (typeof element.namespace != 'undefined') { - const merge_namespace = (typeof element.namespace == 'string') ? { default: element.namespace } : element.namespace; - namespace = merge(namespace, merge_namespace); - } - let namespaced_element, namespaced_property; - for (let property in element) { - if (property === 'namespace') { - continue; - } - namespaced_element = applyNamespaces(element[property], namespace, default_namespace); - namespaced_property = applyNamespaces(property, namespace, default_namespace); - if (property !== namespaced_property) { - delete element[property]; - } - element[namespaced_property] = namespaced_element; + + for (let [field_name, item] of Object.entries(layout)) { + if (field_name === 'namespace') { + Object.keys(item).forEach((requested_ns) => { + const override = shared_namespaces[requested_ns]; + if (override) { + item[requested_ns] = override; + } + }); + } else if (item !== null && (typeof item === 'object')) { + layout[field_name] = applyNamespaces(item, shared_namespaces); } } - return element; + return layout; } /** @@ -146,6 +132,8 @@

Source: helpers/layouts.js

} function deepCopy(item) { + // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future. + // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects return JSON.parse(JSON.stringify(item)); } @@ -167,6 +155,52 @@

Source: helpers/layouts.js

return d3[factory_name] || null; } + +/** + * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided + * data adapters will actually give all the information required to draw the plot. + * @param {Object} layout + * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields, + * and random sentences that match an arbitrary pattern. + * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time + * @return {Set} + */ +function findFields(layout, prefixes, field_finder = null) { + const fields = new Set(); + if (!field_finder) { + if (!prefixes.length) { + // A layer that doesn't ask for external data does not need to check if the provider returns expected fields + return fields; + } + const all_ns = prefixes.join('|'); + + // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`. + // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches + field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\w+)`, 'g'); + } + + for (const value of Object.values(layout)) { + const value_type = typeof value; + let matches = []; + if (value_type === 'string') { + let a_match; + while ((a_match = field_finder.exec(value)) !== null) { + matches.push(a_match[1]); + } + } else if (value !== null && value_type === 'object') { + matches = findFields(value, prefixes, field_finder); + } else { + // Only look for field names in strings or compound values + continue; + } + for (let m of matches) { + fields.add(m); + } + } + return fields; +} + + /** * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string), * replaces *exact match* @@ -250,7 +284,7 @@

Source: helpers/layouts.js

return query(layout, selector); } -export { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, renameField }; +export { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };
@@ -261,7 +295,7 @@

Source: helpers/layouts.js


diff --git a/docs/api/helpers_render.js.html b/docs/api/helpers_render.js.html index bf7e7448..74b8235a 100644 --- a/docs/api/helpers_render.js.html +++ b/docs/api/helpers_render.js.html @@ -121,7 +121,7 @@

Source: helpers/render.js


diff --git a/docs/api/helpers_scalable.js.html b/docs/api/helpers_scalable.js.html index ee25bf7a..356e3845 100644 --- a/docs/api/helpers_scalable.js.html +++ b/docs/api/helpers_scalable.js.html @@ -46,10 +46,10 @@

Source: helpers/scalable.js

* @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails * to match field_value. - * @param {*} input value + * @param {*} value value */ -const if_value = (parameters, input) => { - if (typeof input == 'undefined' || parameters.field_value !== input) { +const if_value = (parameters, value) => { + if (typeof value == 'undefined' || parameters.field_value !== value) { if (typeof parameters.else != 'undefined') { return parameters.else; } else { @@ -72,17 +72,17 @@

Source: helpers/scalable.js

* equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist). * @param {*} parameters.null_value - * @param {*} input value + * @param {*} value value * @returns {*} */ -const numerical_bin = (parameters, input) => { +const numerical_bin = (parameters, value) => { const breaks = parameters.breaks || []; const values = parameters.values || []; - if (typeof input == 'undefined' || input === null || isNaN(+input)) { + if (typeof value == 'undefined' || value === null || isNaN(+value)) { return (parameters.null_value ? parameters.null_value : null); } const threshold = breaks.reduce(function (prev, curr) { - if (+input < prev || (+input >= prev && +input < curr)) { + if (+value < prev || (+value >= prev && +value < curr)) { return prev; } else { return curr; @@ -119,6 +119,7 @@

Source: helpers/scalable.js

* choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color) * * See also: stable_choice. + * @function ordinal_cycle * @param {Object} parameters * @param {Array} parameters.values A list of option values * @return {*} @@ -142,6 +143,9 @@

Source: helpers/scalable.js

* CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields ("item 1, item2"...) * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data, * like a category or gene name. + * + * @function stable_choice + * * @param parameters * @param {Array} [parameters.values] A list of options to choose from * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used @@ -195,19 +199,19 @@

Source: helpers/scalable.js

* colors, shapes, etc. * @parameters {*} parameters.null_value */ -const interpolate = (parameters, input) => { +const interpolate = (parameters, value) => { var breaks = parameters.breaks || []; var values = parameters.values || []; var nullval = (parameters.null_value ? parameters.null_value : null); if (breaks.length < 2 || breaks.length !== values.length) { return nullval; } - if (typeof input == 'undefined' || input === null || isNaN(+input)) { + if (typeof value == 'undefined' || value === null || isNaN(+value)) { return nullval; } - if (+input <= parameters.breaks[0]) { + if (+value <= parameters.breaks[0]) { return values[0]; - } else if (+input >= parameters.breaks[parameters.breaks.length - 1]) { + } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) { return values[breaks.length - 1]; } else { var upper_idx = null; @@ -215,14 +219,14 @@

Source: helpers/scalable.js

if (!idx) { return; } - if (breaks[idx - 1] <= +input && breaks[idx] >= +input) { + if (breaks[idx - 1] <= +value && breaks[idx] >= +value) { upper_idx = idx; } }); if (upper_idx === null) { return nullval; } - const normalized_input = (+input - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]); + const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]); if (!isFinite(normalized_input)) { return nullval; } @@ -231,7 +235,54 @@

Source: helpers/scalable.js

}; -export { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle }; +/** + * Calculate the effect direction based on beta, or the combination of beta and standard error. + * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields. + * + * @function effect_direction + * @param parameters + * @param parameters.'+' The value to return if the effect direction is positive + * @param parameters.'-' The value to return if the effect direction is positive + * @param parameters.beta_field The name of the field containing beta + * @param parameters.stderr_beta_field The name of the field containing stderr_beta + * @param {Object} input This function should receive the entire datum object, rather than one single field + * @returns {null} + */ +function effect_direction(parameters, input) { + if (input === undefined) { + return null; + } + + const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters; + + if (!beta_field || !stderr_beta_field) { + throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`); + } + + const beta_val = input[beta_field]; + const se_val = input[stderr_beta_field]; + + if (beta_val !== undefined) { + if (se_val !== undefined) { + if ((beta_val - 2 * se_val) > 0) { + return plus_result; + } else if ((beta_val + 2 * se_val) < 0) { + return neg_result || null; + } + } else { + if (beta_val > 0) { + return plus_result; + } else if (beta_val < 0) { + return neg_result; + } + } + } + // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid + // about expected data formats for layouts. + return null; +} + +export { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };
@@ -242,7 +293,7 @@

Source: helpers/scalable.js


diff --git a/docs/api/helpers_transforms.js.html b/docs/api/helpers_transforms.js.html index 325a4a67..def5b366 100644 --- a/docs/api/helpers_transforms.js.html +++ b/docs/api/helpers_transforms.js.html @@ -184,7 +184,7 @@

Source: helpers/transforms.js


diff --git a/docs/api/index.html b/docs/api/index.html index 2a55fee9..4f7385c9 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -45,6 +45,7 @@

LocusZoom

LocusZoom is a Javascript/d3 embeddable plugin for interactively visualizing statistical genetic data from customizable sources.

+

This is a low level library aimed at developers who want to customize their own data sharing/visualization. If you are a genetics researcher who just wants to make a fast visualization of your research results, try our user-friendly plot-your-own data services built on LocusZoom.js: my.locuszoom.org and LocalZoom.

Build Status

See https://statgen.github.io/locuszoom/docs/ for full documentation and API reference.

To see functional examples of plots generated with LocusZoom.js see statgen.github.io/locuszoom and statgen.github.io/locuszoom/#examples.

@@ -75,7 +76,7 @@

2. Define Data Sources

Data Sources is an object representing a collection of arbitrarily many sources from which data for the plot can be requested. When adding sources to the collection they must be namespaced so that retrieving specific fields can be done with respect to specific data sources.

Here's an example of defining a data sources object for a remote API:

var data_sources = new LocusZoom.DataSources();
-data_sources.add("assoc", ["AssociationLZ", { url: "http://server.com/api/", params: {source: 1} }]);
+data_sources.add("assoc", ["AssociationLZ", { url: "http://server.com/api/", source: 1 }]);
 

The above example adds an "AssociationLZ" data source (a predefined data source designed to make requests for association data) with a defined URL. The namespace for this data source is "assoc".

Data sources can also be local files:

@@ -96,7 +97,6 @@

3. Define a Layout

{ id: "association", type: "scatter", - fields: ["assoc:position", "assoc:pvalue"], x_axis: { field: "assoc:position" }, y_axis: { field: "assoc:pvalue" } } @@ -116,23 +116,22 @@

4. Put it Together with LocusZoom.populate()

<link rel="stylesheet" type="text/css" href="locuszoom.css"/> </head> <body> - <div id="plot"></div> + <div id="lz-plot"></div> <script type="text/javascript"> var data_sources = new LocusZoom.DataSources(); - data_sources.add("assoc", ["AssociationLZ", { url: "https://server.com/api/single/", params: {source: 1} }]); + data_sources.add("assoc", ["AssociationLZ", { url: "https://server.com/api/single/", source: 1 }]); var layout = { - width: 500, + width: 800, panels: [ { id : "association", - height: 500, + height: 300, data_layers: [ { id: "association", type: "scatter", - fields: ["assoc:position", "assoc:pvalue"], x_axis: { field: "assoc:position" }, - y_axis: { field: "assoc:pvalue" } + y_axis: { field: "assoc:log_pvalue" } } ] } @@ -156,7 +155,7 @@

Build a Layout Using Some Predefined Pieces

width: 1000, height: 500, panels: [ - LocusZoom.Layouts.get("panel", "gwas"), + LocusZoom.Layouts.get("panel", "association"), { id: "custom_panel", ...options @@ -168,10 +167,7 @@

Build a Layout Using Some Predefined Pieces

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:

-
const changes = {
-  label_font_size: 20,
-  transition: false
-};
+
const changes = { label_font_size: 20 };
 LocusZoom.Layouts.get("data_layer", "genes", changes);
 

Predefining State by Building a State Object

@@ -223,7 +219,7 @@

Help and Support


diff --git a/docs/api/index.js.html b/docs/api/index.js.html index 0d0c1466..ff36b2e6 100644 --- a/docs/api/index.js.html +++ b/docs/api/index.js.html @@ -42,11 +42,12 @@

Source: index.js

import { ADAPTERS as Adapters, DATA_LAYERS as DataLayers, - WIDGETS as Widgets, + DATA_OPS as DataFunctions, LAYOUTS as Layouts, MATCHERS as MatchFunctions, SCALABLE as ScaleFunctions, TRANSFORMS as TransformationFunctions, + WIDGETS as Widgets, } from './registry'; @@ -58,6 +59,7 @@

Source: index.js

// Registries for plugin system Adapters, DataLayers, + DataFunctions, Layouts, MatchFunctions, ScaleFunctions, @@ -116,7 +118,7 @@

Source: index.js


diff --git a/docs/api/layouts_index.js.html b/docs/api/layouts_index.js.html index 8a6f7dcc..44d93f24 100644 --- a/docs/api/layouts_index.js.html +++ b/docs/api/layouts_index.js.html @@ -51,14 +51,13 @@

Source: layouts/index.js

* Tooltip Layouts */ const standard_association_tooltip = { - namespace: { 'assoc': 'assoc' }, closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: `<strong>{{{{namespace[assoc]}}variant|htmlescape}}</strong><br> - P Value: <strong>{{{{namespace[assoc]}}log_pvalue|logtoscinotation|htmlescape}}</strong><br> - Ref. Allele: <strong>{{{{namespace[assoc]}}ref_allele|htmlescape}}</strong><br> - {{#if {{namespace[ld]}}isrefvar}}<strong>LD Reference Variant</strong>{{#else}} + html: `<strong>{{assoc:variant|htmlescape}}</strong><br> + P Value: <strong>{{assoc:log_pvalue|logtoscinotation|htmlescape}}</strong><br> + Ref. Allele: <strong>{{assoc:ref_allele|htmlescape}}</strong><br> + {{#if lz_is_ld_refvar}}<strong>LD Reference Variant</strong>{{#else}} <a href="javascript:void(0);" onclick="var data = this.parentNode.__data__; data.getDataLayer().makeLDReference(data);" @@ -94,30 +93,28 @@

Source: layouts/index.js

}; const catalog_variant_tooltip = { - namespace: { 'assoc': 'assoc', 'catalog': 'catalog' }, closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: '<strong>{{{{namespace[catalog]}}variant|htmlescape}}</strong><br>' + html: '<strong>{{catalog:variant|htmlescape}}</strong><br>' + 'Catalog entries: <strong>{{n_catalog_matches|htmlescape}}</strong><br>' - + 'Top Trait: <strong>{{{{namespace[catalog]}}trait|htmlescape}}</strong><br>' - + 'Top P Value: <strong>{{{{namespace[catalog]}}log_pvalue|logtoscinotation}}</strong><br>' + + 'Top Trait: <strong>{{catalog:trait|htmlescape}}</strong><br>' + + 'Top P Value: <strong>{{catalog:log_pvalue|logtoscinotation}}</strong><br>' // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL - + 'More: <a href="https://www.ebi.ac.uk/gwas/search?query={{{{namespace[catalog]}}rsid|htmlescape}}" target="_blank" rel="noopener">GWAS catalog</a> / <a href="https://www.ncbi.nlm.nih.gov/snp/{{{{namespace[catalog]}}rsid|htmlescape}}" target="_blank" rel="noopener">dbSNP</a>', + + 'More: <a href="https://www.ebi.ac.uk/gwas/search?query={{catalog:rsid|htmlescape}}" target="_blank" rel="noopener">GWAS catalog</a> / <a href="https://www.ncbi.nlm.nih.gov/snp/{{catalog:rsid|htmlescape}}" target="_blank" rel="noopener">dbSNP</a>', }; const coaccessibility_tooltip = { - namespace: { 'access': 'access' }, closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element) html: '<strong>Regulatory element</strong><br>' + - '{{{{namespace[access]}}start1|htmlescape}}-{{{{namespace[access]}}end1|htmlescape}}<br>' + + '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}<br>' + '<strong>Promoter</strong><br>' + - '{{{{namespace[access]}}start2|htmlescape}}-{{{{namespace[access]}}end2|htmlescape}}<br>' + - '{{#if {{namespace[access]}}target}}<strong>Target</strong>: {{{{namespace[access]}}target|htmlescape}}<br>{{/if}}' + - '<strong>Score</strong>: {{{{namespace[access]}}score|htmlescape}}', + '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}<br>' + + '{{#if access:target}}<strong>Target</strong>: {{access:target|htmlescape}}<br>{{/if}}' + + '<strong>Score</strong>: {{access:score|htmlescape}}', }; /* @@ -143,22 +140,24 @@

Source: layouts/index.js

* @type data_layer */ const recomb_rate_layer = { - namespace: { 'recomb': 'recomb' }, id: 'recombrate', + namespace: { 'recomb': 'recomb' }, + data_operations: [ + { type: 'fetch', from: ['recomb'] }, + ], type: 'line', tag: 'recombination', - fields: ['{{namespace[recomb]}}position', '{{namespace[recomb]}}recomb_rate'], z_index: 1, style: { 'stroke': '#0000FF', 'stroke-width': '1.5px', }, x_axis: { - field: '{{namespace[recomb]}}position', + field: 'recomb:position', }, y_axis: { axis: 2, - field: '{{namespace[recomb]}}recomb_rate', + field: 'recomb:recomb_rate', floor: 0, ceiling: 100, }, @@ -171,28 +170,52 @@

Source: layouts/index.js

*/ const association_pvalues_layer = { namespace: { 'assoc': 'assoc', 'ld': 'ld' }, + data_operations: [ + { + type: 'fetch', + from: ['assoc', 'ld(assoc)'], + }, + { + type: 'left_match', + name: 'assoc_plus_ld', + requires: ['assoc', 'ld'], + params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later. + }, + ], id: 'associationpvalues', type: 'scatter', tag: 'association', - fields: ['{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', '{{namespace[assoc]}}log_pvalue', '{{namespace[assoc]}}log_pvalue|logtoscinotation', '{{namespace[assoc]}}ref_allele', '{{namespace[ld]}}state', '{{namespace[ld]}}isrefvar'], - id_field: '{{namespace[assoc]}}variant', + id_field: 'assoc:variant', coalesce: { active: true, }, - point_shape: { - scale_function: 'if', - field: '{{namespace[ld]}}isrefvar', - parameters: { - field_value: 1, - then: 'diamond', - else: 'circle', + point_shape: [ + { + scale_function: 'if', + field: 'lz_is_ld_refvar', + parameters: { + field_value: true, + then: 'diamond', + }, }, - }, + { + // Not every dataset will provide these params + scale_function: 'effect_direction', + parameters: { + '+': 'triangle', + '-': 'triangledown', + // The scale function receives the entire datum object, so it needs to be told where to find individual fields + beta_field: 'assoc:beta', + stderr_beta_field: 'assoc:se', + }, + }, + 'circle', + ], point_size: { scale_function: 'if', - field: '{{namespace[ld]}}isrefvar', + field: 'lz_is_ld_refvar', parameters: { - field_value: 1, + field_value: true, then: 80, else: 40, }, @@ -200,15 +223,15 @@

Source: layouts/index.js

color: [ { scale_function: 'if', - field: '{{namespace[ld]}}isrefvar', + field: 'lz_is_ld_refvar', parameters: { - field_value: 1, + field_value: true, then: '#9632b8', }, }, { scale_function: 'numerical_bin', - field: '{{namespace[ld]}}state', + field: 'ld:correlation', parameters: { breaks: [0, 0.2, 0.4, 0.6, 0.8], // Derived from Google "Turbo" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85] @@ -229,11 +252,11 @@

Source: layouts/index.js

label: null, z_index: 2, x_axis: { - field: '{{namespace[assoc]}}position', + field: 'assoc:position', }, y_axis: { axis: 1, - field: '{{namespace[assoc]}}log_pvalue', + field: 'assoc:log_pvalue', floor: 0, upper_buffer: 0.10, min_extent: [0, 10], @@ -258,15 +281,18 @@

Source: layouts/index.js

* @type data_layer */ const coaccessibility_layer = { - namespace: { 'access': 'access' }, id: 'coaccessibility', type: 'arcs', tag: 'coaccessibility', - fields: ['{{namespace[access]}}start1', '{{namespace[access]}}end1', '{{namespace[access]}}start2', '{{namespace[access]}}end2', '{{namespace[access]}}id', '{{namespace[access]}}target', '{{namespace[access]}}score'], - match: { send: '{{namespace[access]}}target', receive: '{{namespace[access]}}target' }, - id_field: '{{namespace[access]}}id', + namespace: { 'access': 'access' }, + data_operations: [ + { type: 'fetch', from: ['access'] }, + ], + match: { send: 'access:target', receive: 'access:target' }, + // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID. + id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}', filters: [ - { field: '{{namespace[access]}}score', operator: '!=', value: null }, + { field: 'access:score', operator: '!=', value: null }, ], color: [ { @@ -293,12 +319,12 @@

Source: layouts/index.js

}, ], x_axis: { - field1: '{{namespace[access]}}start1', - field2: '{{namespace[access]}}start2', + field1: 'access:start1', + field2: 'access:start2', }, y_axis: { axis: 1, - field: '{{namespace[access]}}score', + field: 'access:score', upper_buffer: 0.1, min_extent: [0, 1], }, @@ -325,41 +351,63 @@

Source: layouts/index.js

// Slightly modify an existing layout let base = deepCopy(association_pvalues_layer); base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base); - base.tooltip.html += '{{#if {{namespace[catalog]}}rsid}}<br><a href="https://www.ebi.ac.uk/gwas/search?query={{{{namespace[catalog]}}rsid|htmlescape}}" target="_blank" rel="noopener">See hits in GWAS catalog</a>{{/if}}'; + + base.data_operations.push({ + type: 'assoc_to_gwas_catalog', + name: 'assoc_catalog', + requires: ['assoc_plus_ld', 'catalog'], + params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'], + }); + + base.tooltip.html += '{{#if catalog:rsid}}<br><a href="https://www.ebi.ac.uk/gwas/search?query={{catalog:rsid|htmlescape}}" target="_blank" rel="noopener">See hits in GWAS catalog</a>{{/if}}'; base.namespace.catalog = 'catalog'; - base.fields.push('{{namespace[catalog]}}rsid', '{{namespace[catalog]}}trait', '{{namespace[catalog]}}log_pvalue'); return base; }(); + /** * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API * @name phewas_pvalues * @type data_layer */ const phewas_pvalues_layer = { - namespace: { 'phewas': 'phewas' }, id: 'phewaspvalues', type: 'category_scatter', tag: 'phewas', - point_shape: 'circle', + namespace: { 'phewas': 'phewas' }, + data_operations: [ + { type: 'fetch', from: ['phewas'] }, + ], + point_shape: [ + { + scale_function: 'effect_direction', + parameters: { + '+': 'triangle', + '-': 'triangledown', + // The scale function receives the entire datum object, so it needs to be told where to find individual fields + beta_field: 'phewas:beta', + stderr_beta_field: 'phewas:se', + }, + }, + 'circle', + ], point_size: 70, tooltip_positioning: 'vertical', - id_field: '{{namespace[phewas]}}id', - fields: ['{{namespace[phewas]}}id', '{{namespace[phewas]}}log_pvalue', '{{namespace[phewas]}}trait_group', '{{namespace[phewas]}}trait_label'], + id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}', x_axis: { - field: '{{namespace[phewas]}}x', // Synthetic/derived field added by `category_scatter` layer - category_field: '{{namespace[phewas]}}trait_group', + field: 'lz_auto_x', // Automatically added by the category_scatter layer + category_field: 'phewas:trait_group', lower_buffer: 0.025, upper_buffer: 0.025, }, y_axis: { axis: 1, - field: '{{namespace[phewas]}}log_pvalue', + field: 'phewas:log_pvalue', floor: 0, upper_buffer: 0.15, }, color: [{ - field: '{{namespace[phewas]}}trait_group', + field: 'phewas:trait_group', scale_function: 'categorical_bin', parameters: { categories: [], @@ -372,11 +420,11 @@

Source: layouts/index.js

closable: true, show: { or: ['highlighted', 'selected'] }, hide: { and: ['unhighlighted', 'unselected'] }, - html: [ - '<strong>Trait:</strong> {{{{namespace[phewas]}}trait_label|htmlescape}}<br>', - '<strong>Trait Category:</strong> {{{{namespace[phewas]}}trait_group|htmlescape}}<br>', - '<strong>P-value:</strong> {{{{namespace[phewas]}}log_pvalue|logtoscinotation|htmlescape}}<br>', - ].join(''), + html: `<strong>Trait:</strong> {{phewas:trait_label|htmlescape}}<br> +<strong>Trait Category:</strong> {{phewas:trait_group|htmlescape}}<br> +<strong>P-value:</strong> {{phewas:log_pvalue|logtoscinotation|htmlescape}} +{{#if phewas:beta|is_numeric}}<br><strong>&beta;:</strong> {{phewas:beta|scinotation|htmlescape}}<br>{{/if}} +{{#if phewas:se|is_numeric}}<strong>SE (&beta;):</strong> {{phewas:se|scinotation|htmlescape}}{{/if}}`, }, behaviors: { onmouseover: [ @@ -390,7 +438,7 @@

Source: layouts/index.js

], }, label: { - text: '{{{{namespace[phewas]}}trait_label}}', + text: '{{phewas:trait_label}}', spacing: 6, lines: { style: { @@ -401,7 +449,7 @@

Source: layouts/index.js

}, filters: [ { - field: '{{namespace[phewas]}}log_pvalue', + field: 'phewas:log_pvalue', operator: '>=', value: 20, }, @@ -420,10 +468,20 @@

Source: layouts/index.js

*/ const genes_layer = { namespace: { 'gene': 'gene', 'constraint': 'constraint' }, + data_operations: [ + { + type: 'fetch', + from: ['gene', 'constraint(gene)'], + }, + { + name: 'gene_constraint', + type: 'genes_to_gnomad_constraint', + requires: ['gene', 'constraint'], + }, + ], id: 'genes', type: 'genes', tag: 'genes', - fields: ['{{namespace[gene]}}all', '{{namespace[constraint]}}all'], id_field: 'gene_id', behaviors: { onmouseover: [ @@ -474,23 +532,29 @@

Source: layouts/index.js

const annotation_catalog_layer = { // Identify GWAS hits that are present in the GWAS catalog namespace: { 'assoc': 'assoc', 'catalog': 'catalog' }, + data_operations: [ + { + type: 'fetch', from: ['assoc', 'catalog'], + }, + { + type: 'assoc_to_gwas_catalog', + name: 'assoc_plus_ld', + requires: ['assoc', 'catalog'], + params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'], + }, + ], id: 'annotation_catalog', type: 'annotation_track', tag: 'gwascatalog', - id_field: '{{namespace[assoc]}}variant', + id_field: 'assoc:variant', x_axis: { - field: '{{namespace[assoc]}}position', + field: 'assoc:position', }, color: '#0000CC', - fields: [ - '{{namespace[assoc]}}variant', '{{namespace[assoc]}}chromosome', '{{namespace[assoc]}}position', - '{{namespace[catalog]}}variant', '{{namespace[catalog]}}rsid', '{{namespace[catalog]}}trait', - '{{namespace[catalog]}}log_pvalue', '{{namespace[catalog]}}pos', - ], filters: [ // Specify which points to show on the track. Any selection must satisfy ALL filters - { field: '{{namespace[catalog]}}rsid', operator: '!=', value: null }, - { field: '{{namespace[catalog]}}log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, + { field: 'catalog:rsid', operator: '!=', value: null }, + { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, ], behaviors: { onmouseover: [ @@ -796,7 +860,6 @@

Source: layouts/index.js

let base = deepCopy(association_panel); base = merge({ id: 'associationcatalog', - namespace: { 'assoc': 'assoc', 'ld': 'ld', 'catalog': 'catalog' }, // Required to resolve display options }, base); base.toolbar.widgets.push({ @@ -816,7 +879,7 @@

Source: layouts/index.js

display_name: 'Label catalog traits', // Human readable representation of field name display: { // Specify layout directives that control display of the plot for this option label: { - text: '{{{{namespace[catalog]}}trait}}', + text: '{{catalog:trait}}', spacing: 6, lines: { style: { @@ -828,9 +891,9 @@

Source: layouts/index.js

filters: [ // Only label points if they are significant for some trait in the catalog, AND in high LD // with the top hit of interest - { field: '{{namespace[catalog]}}trait', operator: '!=', value: null }, - { field: '{{namespace[catalog]}}log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, - { field: '{{namespace[ld]}}state', operator: '>', value: 0.4 }, + { field: 'catalog:trait', operator: '!=', value: null }, + { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP }, + { field: 'ld:correlation', operator: '>', value: 0.4 }, ], style: { 'font-size': '10px', @@ -1118,7 +1181,7 @@

Source: layouts/index.js


diff --git a/docs/api/module-LocusZoom-DataSources.html b/docs/api/module-LocusZoom-DataSources.html index 892bd951..2109126d 100644 --- a/docs/api/module-LocusZoom-DataSources.html +++ b/docs/api/module-LocusZoom-DataSources.html @@ -1094,7 +1094,7 @@
Returns:

diff --git a/docs/api/module-LocusZoom.html b/docs/api/module-LocusZoom.html index ba54be6d..cd27a409 100644 --- a/docs/api/module-LocusZoom.html +++ b/docs/api/module-LocusZoom.html @@ -220,6 +220,78 @@
Type:
+

(inner, constant) DataFunctions :module:registry/base~RegistryBase

+ + + + +
+

A plugin registry that allows plots to use both pre-defined and user-provided "data join" functions.

+
+ + + +
Type:
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + +

(inner, constant) DataLayers :module:registry/base~ClassRegistry

@@ -346,7 +418,7 @@
Type:
Source:
@@ -709,7 +781,7 @@
Parameters:
Source:
@@ -1076,7 +1148,7 @@
Parameters:
Source:
@@ -1120,7 +1192,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Adapters-AssociationLZ.html b/docs/api/module-LocusZoom_Adapters-AssociationLZ.html index 756943cc..1cebe25c 100644 --- a/docs/api/module-LocusZoom_Adapters-AssociationLZ.html +++ b/docs/api/module-LocusZoom_Adapters-AssociationLZ.html @@ -87,13 +87,13 @@
Parameters:
- config.url + config.source -string +Number @@ -103,128 +103,7 @@
Parameters:
-

The base URL for the remote data.

- - - - - - - config.params - - - - - -object - - - - - - - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
sort - - - - <optional>
- - - - - -
- - false - -

Whether to sort the association data (by an assumed field named "position"). This -is primarily a site-specific workaround for a particular LZ usage; we encourage apis to sort by position before returning -data to the browser.

source - - - - <optional>
- - - - - -
- -

The ID of the GWAS dataset to use for this request, as matching the API backend

- - +

The source ID for the dataset of interest, used to construct the request URL

@@ -265,7 +144,7 @@
Properties
Source:
@@ -275,7 +154,7 @@
Properties
See:
@@ -320,251 +199,6 @@
Properties
-

Methods

- - - - - - - -

getURL()

- - - - - - -
-

Add query parameters to the URL to construct a query for the specified region

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(protected) normalizeResponse(data) → {Object}

- - - - - - -
-

Some association sources do not sort their data in a predictable order, which makes it hard to reliably -align with other sources (such as LD). For performance reasons, sorting is an opt-in argument. -TODO: Consider more fine grained sorting control in the future. This was added as a very specific -workaround for the original T2D portal.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
data - -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -Object - - -
-
- - - - - - - - - @@ -579,7 +213,7 @@
Returns:

diff --git a/docs/api/module-LocusZoom_Adapters-BaseAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseAdapter.html index 259a9940..67414ecc 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseAdapter.html @@ -29,9 +29,9 @@

Class: BaseAdapter

- LocusZoom_Adapters~BaseAdapter(config)

+ LocusZoom_Adapters~BaseAdapter()

-

Base class for LocusZoom data sources (any). See also: BaseApiAdapter for requests from a remote URL.

+

Replaced with the BaseLZAdapter class.

@@ -46,7 +46,7 @@

Constructor

-

new BaseAdapter(config)

+

new BaseAdapter()

@@ -61,55 +61,6 @@

new BaseAd -

Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
config - - -object - - - -

Configuration options

- - @@ -131,186 +82,7 @@
Parameters:
- - - - - - - - - - -
Source:
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Methods

- - - - - - - -

(protected) annotateData(records, chain) → {Array.<Object>|Promise}

- - - - - - -
-

Hook to post-process the data returned by this source with new, additional behavior. -(eg cleaning up API values or performing complex calculations on the returned data)

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
records - - -Array.<Object> - - - -

The parsed data from the source (eg standardized api response)

chain - - -Object - - - -

The data chain object. For example, chain.headers may provide useful annotation metadata

- - - - - - -
- - - - - - - - - - - - - - - +
Deprecated:
  • Yes
@@ -324,7 +96,7 @@
Parameters:
Source:
@@ -349,210 +121,13 @@
Parameters:
-
Returns:
- - -
-

The modified set of records

-
- - - -
-
- Type -
-
- -Array.<Object> -| - -Promise - - -
-
- - - - - - - - - - - - - -

(protected) combineChainBody(data, chain, fields, outnames, trans) → {Promise|Array.<Object>}

- - - - - - -
-

Combine records from this source with others in the chain to yield final chain body. -Handles merging this data with other sources (if applicable).

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
data - - -Array.<Object> - - - -

The data That would be returned from this source alone

chain - - -Object - - - -

The data chain built up during previous requests

fields - - -Array.<String> - - - -
outnames - - -Array.<String> - - - -
trans - - -Array.<String> - - - -
- - -
+ @@ -571,1522 +146,6 @@
Parameters:
- - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
-

The new chain body

-
- - - -
-
- Type -
-
- -Promise -| - -Array.<Object> - - -
-
- - - - - - - - - - - - - -

(protected) extractFields(data, fields, outnames, trans)

- - - - - - -
-

Clean up the server records for use by datalayers: extract only certain fields, with the specified names. -Apply per-field transformations as appropriate.

-

This hook can be overridden, eg to create a source that always returns all records and ignores the "fields" array. -This is particularly common for sources at the end of a chain- many "dependent" sources do not allow -cherry-picking individual fields, in which case by convention the fields array specifies "last_source_name:all"

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
data - - -Array.<Object> - - - -

One record object per element

fields - - -Array.<String> - - - -

The names of fields to extract (as named in the source data). Eg "afield"

outnames - - -Array.<String> - - - -

How to represent the source fields in the output. Eg "namespace:afield|atransform"

trans - - -Array.<function()> - - - -

An array of transformation functions (if any). One function per data element, or null.

- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(protected) fetchRequest(state, chain, fields) → {Promise}

- - - - - - -
-

Perform a network request to fetch data for this source. This is usually the method that is used to override -when defining how to retrieve data.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
state - - -Object - - - -

The state of the parent plot

chain - -
fields - -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -Promise - - -
-
- - - - - - - - - - - - - -

(protected) getCacheKey(state, chain, fields) → {String}

- - - - - - -
-

A unique identifier that indicates whether cached data is valid for this request. For most sources using GET -requests to a REST API, this is usually the region requested. Some sources will append additional params to define the request.

-

This means that to change caching behavior, both the URL and the cache key may need to be updated. However, -it allows most datasources to skip an extra network request when zooming in.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
state - - -Object - - - -

Information available in plot.state (chr, start, end). Sometimes used to inject globally -available information that influences the request being made.

chain - - -Object - - - -

The data chain from previous requests made in a sequence.

fields - -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -String - - -
-
- - - - - - - - - - - - - -

(protected) getRequest() → {Promise}

- - - - - - -
-

Gets the data for just this source, typically via a network request (but using cache where possible)

-

For most use cases, it is better to override fetchRequest instead, to avoid bypassing the cache mechanism -by accident.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -Promise - - -
-
- - - - - - - - - - - - - -

(protected) getURL()

- - - - - - -
-

Stub: build the URL for any requests made by this source.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(protected) normalizeResponse(data)

- - - - - - -
-

Ensure the server response is in a canonical form, an array of one object per record. [ {field: oneval} ]. -If the server response contains columns, reformats the response from {column1: [], column2: []} to the above.

-

Does not apply namespacing, transformations, or field extraction.

-

May be overridden by data adapters that inherently return more complex payloads, or that exist to annotate other -sources (eg, if the payload provides extra data rather than a series of records).

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
data - - -Array.<Object> -| - -Object - - - -

The original parsed server response

- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(protected) parseInit(config)

- - - - - - -
-

Parse configuration used to create the instance. Many custom sources will override this method to suit their -needs (eg specific config options, or for sources that do not retrieve data from a URL)

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
config - - -String -| - -Object - - - -

Basic configuration- either a url, or a config object

-
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
url - - -String - - - - - - <optional>
- - - - - -

The datasource URL

params - - -String - - - - - - <optional>
- - - - - -

Initial config params for the datasource

- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(protected) parseResponse(resp, chain, fields, outnames, trans) → {Promise}

- - - - - - -
-

Coordinates the work of parsing a response and returning records. This is broken into 4 steps, which may be -overridden separately for fine-grained control. Each step can return either raw data or a promise.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
resp - - -String -| - -Object - - - -

The raw data associated with the response

chain - - -Object - - - -

The combined parsed response data from this and all other requests made in the chain

fields - - -Array.<String> - - - -

Array of requested field names (as they would appear in the response payload)

outnames - - -Array.<String> - - - -

Array of field names as they will be represented in the data returned by this source, -including the namespace. This must be an array with the same length as fields

trans - - -Array.<function()> - - - -

The collection of transformation functions to be run on selected fields. -This must be an array with the same length as fields

- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - -
See:
-
- -
- - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
-

A promise that resolves to an object containing -request metadata (headers: {}), the consolidated data for plotting (body: []), and the individual responses that would be -returned by each source in the chain in isolation (discrete: {})

-
- - - -
-
- Type -
-
- -Promise - - -
-
- - - - - - - - - - - @@ -2099,7 +158,7 @@
Returns:

diff --git a/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html index 9437d30d..9c0708e2 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html @@ -31,7 +31,7 @@

Class: BaseApiAdapter

LocusZoom_Adapters~BaseApiAdapter()

-

Base class for LocusZoom data adapters that receive their data over the web. Adds default config parameters +

Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters (and potentially other behavior) that are relevant to URL-based requests.

@@ -132,6 +132,8 @@
Parameters:
+
Deprecated:
  • Yes
+ @@ -144,7 +146,7 @@
Parameters:
Source:
@@ -203,1950 +205,6 @@

Extends

-

Methods

- - - - - - - -

(protected) annotateData(records, chain) → {Array.<Object>|Promise}

- - - - - - -
-

Hook to post-process the data returned by this source with new, additional behavior. -(eg cleaning up API values or performing complex calculations on the returned data)

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
records - - -Array.<Object> - - - -

The parsed data from the source (eg standardized api response)

chain - - -Object - - - -

The data chain object. For example, chain.headers may provide useful annotation metadata

- - - - - - -
- - - - - - -
Inherited From:
-
- - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
-

The modified set of records

-
- - - -
-
- Type -
-
- -Array.<Object> -| - -Promise - - -
-
- - - - - - - - - - - - - -

(protected) combineChainBody(data, chain, fields, outnames, trans) → {Promise|Array.<Object>}

- - - - - - -
-

Combine records from this source with others in the chain to yield final chain body. -Handles merging this data with other sources (if applicable).

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
data - - -Array.<Object> - - - -

The data That would be returned from this source alone

chain - - -Object - - - -

The data chain built up during previous requests

fields - - -Array.<String> - - - -
outnames - - -Array.<String> - - - -
trans - - -Array.<String> - - - -
- - - - - - -
- - - - - - -
Inherited From:
-
- - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
-

The new chain body

-
- - - -
-
- Type -
-
- -Promise -| - -Array.<Object> - - -
-
- - - - - - - - - - - - - -

(protected) extractFields(data, fields, outnames, trans)

- - - - - - -
-

Clean up the server records for use by datalayers: extract only certain fields, with the specified names. -Apply per-field transformations as appropriate.

-

This hook can be overridden, eg to create a source that always returns all records and ignores the "fields" array. -This is particularly common for sources at the end of a chain- many "dependent" sources do not allow -cherry-picking individual fields, in which case by convention the fields array specifies "last_source_name:all"

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
data - - -Array.<Object> - - - -

One record object per element

fields - - -Array.<String> - - - -

The names of fields to extract (as named in the source data). Eg "afield"

outnames - - -Array.<String> - - - -

How to represent the source fields in the output. Eg "namespace:afield|atransform"

trans - - -Array.<function()> - - - -

An array of transformation functions (if any). One function per data element, or null.

- - - - - - -
- - - - - - -
Inherited From:
-
- - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(protected) fetchRequest(state, chain, fields) → {Promise}

- - - - - - -
-

Perform a network request to fetch data for this source. This is usually the method that is used to override -when defining how to retrieve data.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
state - - -Object - - - -

The state of the parent plot

chain - -
fields - -
- - - - - - -
- - - - - - -
Inherited From:
-
- - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -Promise - - -
-
- - - - - - - - - - - - - -

(protected) getCacheKey(state, chain, fields) → {String}

- - - - - - -
-

A unique identifier that indicates whether cached data is valid for this request. For most sources using GET -requests to a REST API, this is usually the region requested. Some sources will append additional params to define the request.

-

This means that to change caching behavior, both the URL and the cache key may need to be updated. However, -it allows most datasources to skip an extra network request when zooming in.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
state - - -Object - - - -

Information available in plot.state (chr, start, end). Sometimes used to inject globally -available information that influences the request being made.

chain - - -Object - - - -

The data chain from previous requests made in a sequence.

fields - -
- - - - - - -
- - - - - - -
Inherited From:
-
- - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -String - - -
-
- - - - - - - - - - - - - -

(protected) getRequest() → {Promise}

- - - - - - -
-

Gets the data for just this source, typically via a network request (but using cache where possible)

-

For most use cases, it is better to override fetchRequest instead, to avoid bypassing the cache mechanism -by accident.

-
- - - - - - - - - - - - - -
- - - - - - -
Inherited From:
-
- - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -Promise - - -
-
- - - - - - - - - - - - - -

(protected) getURL()

- - - - - - -
-

Stub: build the URL for any requests made by this source.

-
- - - - - - - - - - - - - -
- - - - - - -
Inherited From:
-
- - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(protected) normalizeResponse(data)

- - - - - - -
-

Ensure the server response is in a canonical form, an array of one object per record. [ {field: oneval} ]. -If the server response contains columns, reformats the response from {column1: [], column2: []} to the above.

-

Does not apply namespacing, transformations, or field extraction.

-

May be overridden by data adapters that inherently return more complex payloads, or that exist to annotate other -sources (eg, if the payload provides extra data rather than a series of records).

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
data - - -Array.<Object> -| - -Object - - - -

The original parsed server response

- - - - - - -
- - - - - - -
Inherited From:
-
- - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(protected) parseInit(config)

- - - - - - -
-

Parse configuration used to create the instance. Many custom sources will override this method to suit their -needs (eg specific config options, or for sources that do not retrieve data from a URL)

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
config - - -String -| - -Object - - - -

Basic configuration- either a url, or a config object

-
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
url - - -String - - - - - - <optional>
- - - - - -

The datasource URL

params - - -String - - - - - - <optional>
- - - - - -

Initial config params for the datasource

- -
- - - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(protected) parseResponse(resp, chain, fields, outnames, trans) → {Promise}

- - - - - - -
-

Coordinates the work of parsing a response and returning records. This is broken into 4 steps, which may be -overridden separately for fine-grained control. Each step can return either raw data or a promise.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
resp - - -String -| - -Object - - - -

The raw data associated with the response

chain - - -Object - - - -

The combined parsed response data from this and all other requests made in the chain

fields - - -Array.<String> - - - -

Array of requested field names (as they would appear in the response payload)

outnames - - -Array.<String> - - - -

Array of field names as they will be represented in the data returned by this source, -including the namespace. This must be an array with the same length as fields

trans - - -Array.<function()> - - - -

The collection of transformation functions to be run on selected fields. -This must be an array with the same length as fields

- - - - - - -
- - - - - - -
Inherited From:
-
- - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - -
See:
-
- -
- - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
-

A promise that resolves to an object containing -request metadata (headers: {}), the consolidated data for plotting (body: []), and the individual responses that would be -returned by each source in the chain in isolation (discrete: {})

-
- - - -
-
- Type -
-
- -Promise - - -
-
- - - - - - - - - @@ -2161,7 +219,7 @@
Returns:

diff --git a/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html new file mode 100644 index 00000000..ccd7a65e --- /dev/null +++ b/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html @@ -0,0 +1,606 @@ + + + + + JSDoc: Class: BaseLZAdapter + + + + + + + + + + +
+ +

Class: BaseLZAdapter

+ + + + + + +
+ +
+ +

+ LocusZoom_Adapters~BaseLZAdapter(config, limit_fieldsopt)

+ + +
+ +
+
+ + + + + + +

new BaseLZAdapter(config, limit_fieldsopt)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
config + + +object + + + + + + + + + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
cache_enabled + + + + <optional>
+ + + + + +
+ + true + +
cache_size + + + + <optional>
+ + + + + +
+ + 3 + +
url + + + + <optional>
+ + + + + +
+ +
prefix_namespace + + + + <optional>
+ + + + + +
+ + true + +

Whether to modify the API response by prepending namespace to each field name. +Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant) +Typically, this is only disabled if the response payload is very unusual

+ +
limit_fields + + +Array.<String> + + + + + + <optional>
+ + + + + +
+ + null + +

If an API returns far more data than is needed, this can be used to simplify +the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

_findPrefixedKey(a_record, fieldname)

+ + + + + + +
+

Convenience method, manually called in LZ sources that deal with dependent data.

+

In the last step of fetching data, LZ adds a prefix to each field name. +This means that operations like "build query based on prior data" can't just ask for "log_pvalue" because +they are receiving "assoc:log_pvalue" or some such unknown prefix.

+

This helper lets us use dependent data more easily. Not every adapter needs to use this method.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
a_record + + +Object + + + +

One record (often the first one in a set of records)

fieldname + + +String + + + +

The desired fieldname, eg "log_pvalue"

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html new file mode 100644 index 00000000..89797389 --- /dev/null +++ b/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html @@ -0,0 +1,282 @@ + + + + + JSDoc: Class: BaseUMAdapter + + + + + + + + + + +
+ +

Class: BaseUMAdapter

+ + + + + + +
+ +
+ +

+ LocusZoom_Adapters~BaseUMAdapter(config)

+ +

The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies +of one particular web server.

+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new BaseUMAdapter(config)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
config + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
build + + +String + + + + + + <optional>
+ + + + + +

The genome build to be used by all requests for this adapter.

+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/api/module-LocusZoom_Adapters-ConnectorSource.html b/docs/api/module-LocusZoom_Adapters-ConnectorSource.html deleted file mode 100644 index d0a9371d..00000000 --- a/docs/api/module-LocusZoom_Adapters-ConnectorSource.html +++ /dev/null @@ -1,283 +0,0 @@ - - - - - JSDoc: Class: ConnectorSource - - - - - - - - - - -
- -

Class: ConnectorSource

- - - - - - -
- -
- -

- LocusZoom_Adapters~ConnectorSource()

- -

Base class for "connectors"- this is a highly specialized kind of adapter that is rarely used in most LocusZoom -deployments. This is meant to be subclassed, rather than used directly.

-

A connector is a data adapter that makes no server requests and caches no data of its own. Instead, it decides how to -combine data from other sources in the chain. Connectors are useful when we want to request (or calculate) some -useful piece of information once, but apply it to many different kinds of record types.

-

Typically, a subclass will implement the field merging logic in combineChainBody.

- - -
- -
-
- - - - -

Constructor

- - - -

new ConnectorSource()

- - - - - - - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
config.params - - -Object - - - -

Additional parameters

-
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
sources - - -Object - - - -

Specify how the hard-coded logic should find the data it relies on in the chain, -as {internal_name: chain_source_id} pairs. This allows writing a reusable connector that does not need to make -assumptions about what namespaces a source is using. *

- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - -
See:
-
- -
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- - - - - - - \ No newline at end of file diff --git a/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html b/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html index 398e5f5c..c5965f25 100644 --- a/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html +++ b/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html @@ -90,41 +90,6 @@
Parameters:
- - - config.params.fields.log_pvalue - - - - - -String - - - - - - - - - - - - - - - - - - - - - -

The name of the field containing -log10 pvalue information

- - - - config.params.threshold @@ -243,7 +208,7 @@
Parameters:
Source:
@@ -312,7 +277,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html b/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html index 6680d9b6..17fc62e0 100644 --- a/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html +++ b/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html @@ -77,6 +77,8 @@
Parameters:
Type + Attributes + @@ -102,61 +104,25 @@
Parameters:
- - - -

The base URL for the remote data

- - - - - - - config.params - - - - + -Object - - - + + + - -
Properties
- - - - - - - - - - - - - - - - - - - - + + - - + @@ -185,13 +151,6 @@
Properties
NameTypeAttributesDescription

The base URL for the remote data

buildconfig.build @@ -177,7 +143,7 @@
Properties
-

The genome build to use when calculating LD relative to a specified reference variant. +

The genome build to use May be overridden by a global parameter plot.state.genome_build so that all datasets can be fetched for the appropriate build in a consistent way.

- - - - - - - @@ -226,7 +185,7 @@
Properties
Source:
@@ -236,7 +195,7 @@
Properties
See:
@@ -289,184 +248,7 @@

Methods

-

combineChainBody()

- - - - - - -
-

Annotate GENCODE data (from a previous request to the genes adapter) with additional gene constraint data from -the gnomAD API. See class description for a summary of how this works.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

getURL()

- - - - - - -
-

GraphQL API: request details are encoded in the body, not the URL

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

normalizeResponse()

+

_normalizeResponse()

@@ -474,8 +256,7 @@

norm
-

The gnomAD API has a very complex internal data format. Bypass any record parsing, and provide the data layer with -the exact information returned by the API.

+

The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.

@@ -519,7 +300,7 @@

norm
Source:
@@ -565,7 +346,7 @@

norm
diff --git a/docs/api/module-LocusZoom_Adapters-GeneLZ.html b/docs/api/module-LocusZoom_Adapters-GeneLZ.html index 482f4864..f1b965a0 100644 --- a/docs/api/module-LocusZoom_Adapters-GeneLZ.html +++ b/docs/api/module-LocusZoom_Adapters-GeneLZ.html @@ -74,6 +74,8 @@

Parameters:
Type + Attributes + @@ -99,61 +101,25 @@
Parameters:
- - - -

The base URL for the remote data

- - - - - - - config.params - - - - + -Object - - - + + + - -
Properties
- - - - - - - - - - - - - - - - - - - - + + - - + @@ -182,7 +148,7 @@
Properties
- +
NameTypeAttributesDescription

The base URL for the remote data

buildconfig.build @@ -174,7 +140,7 @@
Properties
-

The genome build to use when calculating LD relative to a specified reference variant. +

The genome build to use May be overridden by a global parameter plot.state.genome_build so that all datasets can be fetched for the appropriate build in a consistent way.

sourceconfig.source @@ -216,13 +182,6 @@
Properties
- - - - - - - @@ -257,7 +216,7 @@
Properties
Source:
@@ -267,7 +226,7 @@
Properties
See:
@@ -320,97 +279,7 @@

Methods

-

extractFields()

- - - - - - -
-

Does not attempt to namespace or modify the fields from the API payload; the payload format is very complex and -quite coupled with the data rendering implementation. -Typically, requests to this adapter specify a single dummy field sufficient to trigger the request: fields:[ 'gene:all' ]

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

getURL()

+

_getURL()

@@ -462,7 +331,7 @@

getURLSource:
@@ -492,160 +361,6 @@

getURLnormalizeResponse(data) → {Array.<Object>|Object}

- - - - - - -
-

The UM genes API has a very complex internal data format. Bypass any record parsing, and provide the data layer with -the exact information returned by the API. (ignoring the fields array in the layout)

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
data - -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -Array.<Object> -| - -Object - - -
-
- - - - - - - @@ -662,7 +377,7 @@
Returns:

diff --git a/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html b/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html index f8e6ff2d..0249e9a1 100644 --- a/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html +++ b/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html @@ -80,6 +80,8 @@
Parameters:
Type + Attributes + @@ -105,61 +107,25 @@
Parameters:
- - - -

The base URL for the remote data.

- - - - - - - config.params - - - - + -Object - - - + + + - -
Properties
- - - - - - - - - - - - - - - - - - - - + + - - + @@ -188,7 +154,7 @@
Properties
- +
NameTypeAttributesDescription

The base URL for the remote data.

buildconfig.build @@ -180,7 +146,7 @@
Properties
-

The genome build to use when calculating LD relative to a specified reference variant. +

The genome build to use when requesting the specific genomic region. May be overridden by a global parameter plot.state.genome_build so that all datasets can be fetched for the appropriate build in a consistent way.

sourceconfig.source @@ -222,13 +188,6 @@
Properties
- - - - - - - @@ -263,7 +222,7 @@
Properties
Source:
@@ -273,7 +232,7 @@
Properties
See:
@@ -326,96 +285,7 @@

Methods

-

combineChainBody()

- - - - - - -
-

Intelligently combine the LD data with the association data used for this data layer. See class description -for a summary of how this works.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

getURL()

+

_getURL()

@@ -467,7 +337,7 @@

getURLSource:
@@ -513,7 +383,7 @@

getURL
diff --git a/docs/api/module-LocusZoom_Adapters-IntervalLZ.html b/docs/api/module-LocusZoom_Adapters-IntervalLZ.html index a8673752..96b34d61 100644 --- a/docs/api/module-LocusZoom_Adapters-IntervalLZ.html +++ b/docs/api/module-LocusZoom_Adapters-IntervalLZ.html @@ -143,7 +143,7 @@
Parameters:
Source:
@@ -153,7 +153,7 @@
Parameters:
See:
@@ -214,7 +214,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Adapters-LDServer.html b/docs/api/module-LocusZoom_Adapters-LDServer.html index 0487f8e3..6d6a469a 100644 --- a/docs/api/module-LocusZoom_Adapters-LDServer.html +++ b/docs/api/module-LocusZoom_Adapters-LDServer.html @@ -33,9 +33,11 @@

Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant. If no plot.state.ldrefvar is explicitly provided, this source will attempt to find the most significant GWAS -variant (smallest pvalue or largest neg_log_pvalue) and yse that as the LD reference variant.

+variant and yse that as the LD reference variant.

+

THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS variant and log_pvalue. It may not work correctly +if this information is not provided.

This source is designed to connect its results to association data, and therefore depends on association data having -been loaded by a previous request in the data chain. For custom association APIs, some additional options might +been loaded by a previous request. For custom association APIs, some additional options might need to be be specified in order to locate the most significant SNP. Variant IDs of the form chrom:pos_ref/alt are preferred, but this source will attempt to harmonize other common data formats into something that the LD server can understand.

@@ -81,8 +83,12 @@

Parameters:
Type + Attributes + + Default + Description @@ -106,63 +112,29 @@
Parameters:
- - - -

The base URL for the remote data.

- - - - - - - config.params - - - - + -object - - - + + + - - -
Properties
+ + + - - - - - - - - - - - - - - - - - - - - + + - - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription

The base URL for the remote data.

buildconfig.build @@ -197,7 +169,7 @@
Properties
sourceconfig.source @@ -232,7 +204,7 @@
Properties
populationconfig.population @@ -268,7 +240,7 @@
Properties
methodconfig.method @@ -299,115 +271,6 @@
Properties
id_field - - - - <optional>
- - - - - -
- -

The association data field that contains variant identifier information. The preferred -format of LD server is chrom:pos_ref/alt and this source will attempt to normalize other common formats. -This source can auto-detect possible matches for field names containing "variant" or "id"

position_field - - - - <optional>
- - - - - -
- -

The association data field that contains variant position information. -This source can auto-detect possible matches for field names containing "position" or "pos"

pvalue_field - - - - <optional>
- - - - - -
- -

The association data field that contains pvalue information. -This source can auto-detect possible matches for field names containing "pvalue" or "log_pvalue". -The suggested LD refvar will be the smallest pvalue, or the largest log_pvalue: this source will auto-detect -the word "log" in order to determine the sign of the comparison.

- - - - - @@ -445,7 +308,7 @@
Properties
Source:
@@ -455,7 +318,7 @@
Properties
See:
@@ -499,665 +362,6 @@
Properties
- -

Methods

- - - - - - - -

combineChainBody()

- - - - - - -
-

The LD adapter attempts to intelligently match retrieved LD information to a request for association data earlier in the data chain. -Since each layer only asks for the data needed for that layer, one LD call is sufficient to annotate many separate association tracks.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

fetchRequest()

- - - - - - -
-

The LDServer API is paginated, but we need all of the data to render a plot. Depaginate and combine where appropriate.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

getCacheKey(state, chain, fields) → {string}

- - - - - - -
-

The LD adapter caches based on region, reference panel, and population name

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
state - -
chain - -
fields - -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -string - - -
-
- - - - - - - - - - - - - -

(protected) getRefvar() → {Array.<String>}

- - - - - - -
-

Get the LD reference variant, which by default will be the most significant hit in the assoc results -This will be used in making the original query to the LD server for pairwise LD information.

-

This is meant to join a single LD request to any number of association results, and to work with many kinds of API. -To do this, the datasource looks for fields with special known names such as pvalue, log_pvalue, etc. -If your API uses different nomenclature, an option must be specified.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
-

Two strings: 1) the marker id (expected to be in chr:pos_ref/alt format) of the reference -variant, and 2) the marker ID as it appears in the original dataset that we are joining to, so that the exact -refvar can be marked when plotting the data..

-
- - - -
-
- Type -
-
- -Array.<String> - - -
-
- - - - - - - - - - - - - -

getURL()

- - - - - - -
-

Identify (or guess) the LD reference variant, then add query parameters to the URL to construct a query for the specified region

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

normalizeResponse()

- - - - - - -
-

The LD API payload does not obey standard format conventions; do not try to transform it.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - @@ -1173,7 +377,7 @@

norm
diff --git a/docs/api/module-LocusZoom_Adapters-PheWASLZ.html b/docs/api/module-LocusZoom_Adapters-PheWASLZ.html index 762c3b24..060a7d2c 100644 --- a/docs/api/module-LocusZoom_Adapters-PheWASLZ.html +++ b/docs/api/module-LocusZoom_Adapters-PheWASLZ.html @@ -109,49 +109,7 @@

Parameters:
- config.params - - - - - -Object - - - - - - - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - + - - - - -
NameTypeDescription
buildconfig.build @@ -168,14 +126,7 @@
Properties

This datasource expects to be provided the name of the genome build that will -be used to provide pheWAS results for this position. Note positions may not translate between builds.

- - +be used to provide PheWAS results for this position. Note positions may not translate between builds.

@@ -216,7 +167,7 @@
Properties
Source:
@@ -226,7 +177,7 @@
Properties
See:
@@ -285,7 +236,7 @@
Properties

diff --git a/docs/api/module-LocusZoom_Adapters-RecombLZ.html b/docs/api/module-LocusZoom_Adapters-RecombLZ.html index 1f29c68b..533587c1 100644 --- a/docs/api/module-LocusZoom_Adapters-RecombLZ.html +++ b/docs/api/module-LocusZoom_Adapters-RecombLZ.html @@ -74,6 +74,8 @@
Parameters:
Type + Attributes + @@ -99,61 +101,25 @@
Parameters:
- - - -

The base URL for the remote data

- - - - - - - config.params - - - - + -Object - - - + + + - -
Properties
- - - - - - - - - - - - - - - - - - - - + + - - + @@ -182,7 +148,7 @@
Properties
- +
NameTypeAttributesDescription

The base URL for the remote data

buildconfig.build @@ -174,7 +140,7 @@
Properties
-

The genome build to use when calculating LD relative to a specified reference variant. +

The genome build to use May be overridden by a global parameter plot.state.genome_build so that all datasets can be fetched for the appropriate build in a consistent way.

sourceconfig.source @@ -216,13 +182,6 @@
Properties
- - - - - - - @@ -257,7 +216,7 @@
Properties
Source:
@@ -267,7 +226,7 @@
Properties
See:
@@ -320,7 +279,7 @@

Methods

-

getURL()

+

_getURL()

@@ -372,7 +331,7 @@

getURLSource:
@@ -418,7 +377,7 @@

getURL
diff --git a/docs/api/module-LocusZoom_Adapters-StaticSource.html b/docs/api/module-LocusZoom_Adapters-StaticSource.html index 0b2a9386..82e6c57e 100644 --- a/docs/api/module-LocusZoom_Adapters-StaticSource.html +++ b/docs/api/module-LocusZoom_Adapters-StaticSource.html @@ -29,7 +29,7 @@

Class: StaticSource

- LocusZoom_Adapters~StaticSource(data)

+ LocusZoom_Adapters~StaticSource()

Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required for some sources (eg it does not know how to join together LD and association data).

@@ -52,7 +52,7 @@

Constructor

-

new StaticSource(data)

+

new StaticSource()

@@ -92,7 +92,7 @@
Parameters:
- data + config.data @@ -149,7 +149,7 @@
Parameters:
Source:
@@ -159,7 +159,7 @@
Parameters:
See:
@@ -218,7 +218,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html b/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html index 4fc2a8dc..82296ff5 100644 --- a/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html +++ b/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html @@ -201,7 +201,46 @@
Parameters:
- config.params.overfetch + config.reader + + + + + +Promise.<Reader> + + + + + + + + + <optional>
+ + + + + + + + + + + + + + +

The URL for tabix-reader instance that provides the data. Mutually exclusive with providing a URL. +Most LocusZoom usages will not pass an external reader. This option exists for websites like LocalZoom that accept +many file formats and want to perform input validation before creating the plot: "select parser options", etc.

+ + + + + + + config.overfetch @@ -276,7 +315,7 @@
Parameters:
Source:
@@ -288,7 +327,7 @@
Parameters:
@@ -347,7 +386,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Adapters-UserTabixLD.html b/docs/api/module-LocusZoom_Adapters-UserTabixLD.html new file mode 100644 index 00000000..532627f0 --- /dev/null +++ b/docs/api/module-LocusZoom_Adapters-UserTabixLD.html @@ -0,0 +1,201 @@ + + + + + JSDoc: Class: UserTabixLD + + + + + + + + + + +
+ +

Class: UserTabixLD

+ + + + + + +
+ +
+ +

+ LocusZoom_Adapters~UserTabixLD()

+ +

Load user-provided LD from a tabix file, and filter the returned set of records based on a reference variant. (will attempt to choose a reference variant based on the most significant association variant, if no state.ldrefvar is specified)

+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new UserTabixLD()

+ + + + + + + + +
Extends:
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + +
See:
+
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + +

Extends

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/api/module-LocusZoom_Adapters.html b/docs/api/module-LocusZoom_Adapters.html index 75e3f6d0..7b22028a 100644 --- a/docs/api/module-LocusZoom_Adapters.html +++ b/docs/api/module-LocusZoom_Adapters.html @@ -55,8 +55,8 @@

Creating data adapters

The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data layer that asks for fields from trait1:fieldname or trait2:fieldname.

const data_sources = new LocusZoom.DataSources();
-data_sources.add("trait1", ["AssociationLZ", {url: "http://server.com/api/single/", params: {source: 1}}]);
-data_sources.add("trait2", ["AssociationLZ", {url: "http://server.com/api/single/", params: {source: 2}}]);
+data_sources.add("trait1", ["AssociationLZ", { url: "http://server.com/api/single/", source: 1 }]);
+data_sources.add("trait2", ["AssociationLZ", { url: "http://server.com/api/single/", source: 2 }]);
 

These data sources are then passed to the plot when data is to be rendered: const plot = LocusZoom.populate("#lz-plot", data_sources, layout);

@@ -159,7 +159,10 @@

Classes

BaseApiAdapter
-
ConnectorSource
+
BaseLZAdapter
+
+ +
BaseUMAdapter
CredibleSetLZ
@@ -191,6 +194,9 @@

Classes

TabixUrlSource
+ +
UserTabixLD
+

@@ -359,7 +365,7 @@

(inner) St
diff --git a/docs/api/module-LocusZoom_DataFunctions.html b/docs/api/module-LocusZoom_DataFunctions.html new file mode 100644 index 00000000..320fab38 --- /dev/null +++ b/docs/api/module-LocusZoom_DataFunctions.html @@ -0,0 +1,1132 @@ + + + + + JSDoc: Module: LocusZoom_DataFunctions + + + + + + + + + + +
+ +

Module: LocusZoom_DataFunctions

+ + + + + + +
+ +
+ + + + + +
+ +
+
+ + +

"Data operation" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results

+

After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation +is a "join", such as combining association + LD together into a single set of records for plotting. Several join +functions (that operate by analogy to SQL) are provided built-in.

+

Other use cases (even if no examples are in the built in code, see unit tests for what is possible):

+
    +
  1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state. +(in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data, +this is the recommended path to do so)
  2. +
  3. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot), +a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg, +for datasets where the categories are not known before first render, this could generate automatic x-axis ticks +(PheWAS), automatic panel legends or color schemes (BED tracks), etc.
  4. +
+

Usually, a data operation receives two recordsets (the left and right members of the join, like "assoc" and "ld"). +In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network +requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is +uncommon. (if possible, try to provide your data with fewer adapters/network requests!)

+

In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some, +particularly for advanced features, may carry assumptions about field names/ formatting. +(example: choosing the best EBI GWAS catalog entry for a variant may look for a field called log_pvalue instead of pvalue, +or it may match two datasets based on a specific way of identifying the variant)

+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

(inner) assoc_to_gwas_catalog(plot_state, recordsets, assoc_key, catalog_key, catalog_log_p_name)

+ + + + + + +
+

A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
plot_state + + +Object + + + +
recordsets + + +Array.<Array> + + + +

An array with two items: assoc records, then catalog records

assoc_key + + +String + + + +

The name of the key field in association data, eg variant ID

catalog_key + + +String + + + +

The name of the key field in gwas catalog data, eg variant ID

catalog_log_p_name + + +String + + + +

The name of the "log_pvalue" field in gwas catalog data, used to choose the most significant claim for a given variant

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) full_outer_match(plot_state, recordsets, left_key)

+ + + + + + +
+

Perform a full outer join, based on records where the field values at left_key and right_key are identical

+

By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
plot_state + + +Object + + + +
recordsets + + +Array.<Array> + + + +
left_key + + +String + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) genes_to_gnomad_constraint(plot_state, recordsets)

+ + + + + + +
+

A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).

+

This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
plot_state + + +Object + + + +
recordsets + + +Array.<Array> + + + +

An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) inner_match(plot_state, recordsets, left_key)

+ + + + + + +
+

Perform an inner join, based on records where the field values at left_key and right_key are identical

+

By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
plot_state + + +Object + + + +
recordsets + + +Array.<Array> + + + +
left_key + + +String + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) left_match(plot_state, recordsets, left_key)

+ + + + + + +
+

Perform a left outer join, based on records where the field values at left_key and right_key are identical

+

By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
plot_state + + +Object + + + +
recordsets + + +Array.<Array> + + + +
left_key + + +String + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html b/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html index cf404277..e272e9b3 100644 --- a/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html +++ b/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html @@ -212,13 +212,13 @@

Parameters:
- layout.fields + layout.id_field -Array.<String> +string @@ -227,6 +227,8 @@
Parameters:
+ <optional>
+ @@ -240,23 +242,24 @@
Parameters:
-

A list of (namespaced) fields specifying what data is used by the layer. Only -these fields will be made available to the data layer, and only data sources (namespaces) referred to in -this array will be fetched. This represents the "contract" between what data is returned and what data is rendered. -This fields array works in concert with the data retrieval method BaseAdapter.extractFields.

+

The datum field used for unique element IDs when addressing DOM elements, mouse +events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is +used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely +identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is +your job to assure that all of the expected fields are present in every element)

- layout.id_field + layout.namespace -string +object @@ -265,7 +268,46 @@
Parameters:
- <optional>
+ + + + + + + + + + + + + +

A set of key value pairs representing how to map the local usage of data ("assoc") +to the globally unique name for something defined in LocusZoom.DataSources. +Namespaces allow a single layout to be reused to plot many tracks of the same type: "given some form of association data, plot it". +These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse +a layout with a new provider of data- like plotting two association studies stacked together- +only the namespace section of the layout needs to be overridden. +Eg, LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})

+ + + + + + + layout.data_operations + + + + + +Array.<module:LocusZoom_DataLayers~DataOperation> + + + + + + + @@ -280,8 +322,7 @@
Parameters:
-

The datum field used for unique element IDs when addressing DOM elements, mouse -events, etc. This should be unique to the specified datum.

+

A set of data operations that will be performed on the data returned from the data adapters specified in namespace. (see: module:LocusZoom_DataFunctions)

@@ -1544,7 +1585,7 @@
Parameters:
Source:
@@ -1594,7 +1635,7 @@

Members

-

(protected, static, constant) default_layout :Object

+

(protected, static, constant) default_layout

@@ -1605,16 +1646,6 @@

(protected, s -
Type:
-
    -
  • - -Object - - -
  • -
- @@ -1648,7 +1679,7 @@
Type:
Source:
@@ -1722,7 +1753,7 @@
Type:
Source:
@@ -1794,7 +1825,7 @@
Type:
Source:
@@ -1869,7 +1900,7 @@
Type:
Source:
@@ -1937,7 +1968,7 @@
Type:
Source:
@@ -2005,7 +2036,7 @@
Type:
Source:
@@ -2271,7 +2302,7 @@
Parameters:
Source:
@@ -2423,7 +2454,7 @@
Parameters:
Source:
@@ -2586,7 +2617,7 @@
Parameters:
Source:
@@ -2698,7 +2729,7 @@

(prote
Source:
@@ -2807,7 +2838,7 @@

(protected)
Source:
@@ -3008,7 +3039,7 @@
Parameters:
Source:
@@ -3072,6 +3103,9 @@

(protected)

Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.

+

The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that +element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data), +a unique ID can be generated via a template expression like {{phewas:pheno}}-{{phewas:trait_label}}

@@ -3123,7 +3157,7 @@
Parameters:
- +

The data associated with a particular element

@@ -3164,7 +3198,7 @@
Parameters:
Source:
@@ -3371,7 +3405,7 @@
Parameters:
Source:
@@ -3486,7 +3520,7 @@

moveBackSource:
@@ -3592,7 +3626,7 @@

moveForwar
Source:
@@ -3640,6 +3674,96 @@

Returns:
+ + + + + + +

mutateLayout()

+ + + + + + +
+

A list of operations that should be run when the layout is mutated +Typically, these are things done once when a layout is first specified, that would not automatically +update when the layout was changed.

+
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -3694,7 +3818,7 @@

renderSource:
@@ -3884,7 +4008,7 @@
Parameters:
Source:
@@ -4019,7 +4143,7 @@
Parameters:
Source:
@@ -4065,7 +4189,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_DataLayers-annotation_track.html b/docs/api/module-LocusZoom_DataLayers-annotation_track.html index 3bab3354..2a577289 100644 --- a/docs/api/module-LocusZoom_DataLayers-annotation_track.html +++ b/docs/api/module-LocusZoom_DataLayers-annotation_track.html @@ -391,7 +391,7 @@

(static, cons
diff --git a/docs/api/module-LocusZoom_DataLayers-arcs.html b/docs/api/module-LocusZoom_DataLayers-arcs.html index 0d852550..6802d2ad 100644 --- a/docs/api/module-LocusZoom_DataLayers-arcs.html +++ b/docs/api/module-LocusZoom_DataLayers-arcs.html @@ -622,7 +622,7 @@

(static, cons
diff --git a/docs/api/module-LocusZoom_DataLayers-category_forest.html b/docs/api/module-LocusZoom_DataLayers-category_forest.html index 5a827cdd..19193239 100644 --- a/docs/api/module-LocusZoom_DataLayers-category_forest.html +++ b/docs/api/module-LocusZoom_DataLayers-category_forest.html @@ -96,7 +96,7 @@

new ca
Source:
@@ -167,7 +167,7 @@

new ca
diff --git a/docs/api/module-LocusZoom_DataLayers-category_scatter.html b/docs/api/module-LocusZoom_DataLayers-category_scatter.html index 56c7606f..8e42c512 100644 --- a/docs/api/module-LocusZoom_DataLayers-category_scatter.html +++ b/docs/api/module-LocusZoom_DataLayers-category_scatter.html @@ -475,7 +475,7 @@

Returns:

diff --git a/docs/api/module-LocusZoom_DataLayers-forest.html b/docs/api/module-LocusZoom_DataLayers-forest.html index c3e70cd0..aa733bde 100644 --- a/docs/api/module-LocusZoom_DataLayers-forest.html +++ b/docs/api/module-LocusZoom_DataLayers-forest.html @@ -598,7 +598,7 @@
Fires:

diff --git a/docs/api/module-LocusZoom_DataLayers-genes.html b/docs/api/module-LocusZoom_DataLayers-genes.html index 35a2d5b4..327934df 100644 --- a/docs/api/module-LocusZoom_DataLayers-genes.html +++ b/docs/api/module-LocusZoom_DataLayers-genes.html @@ -1224,7 +1224,7 @@

render
diff --git a/docs/api/module-LocusZoom_DataLayers-highlight_regions.html b/docs/api/module-LocusZoom_DataLayers-highlight_regions.html index 6b35ce91..d1525458 100644 --- a/docs/api/module-LocusZoom_DataLayers-highlight_regions.html +++ b/docs/api/module-LocusZoom_DataLayers-highlight_regions.html @@ -542,7 +542,7 @@

(static, cons
diff --git a/docs/api/module-LocusZoom_DataLayers-intervals.html b/docs/api/module-LocusZoom_DataLayers-intervals.html index f1924a70..4a88586b 100644 --- a/docs/api/module-LocusZoom_DataLayers-intervals.html +++ b/docs/api/module-LocusZoom_DataLayers-intervals.html @@ -687,7 +687,7 @@
Parameters:
Source:
@@ -808,7 +808,7 @@

_
Source:
@@ -900,7 +900,7 @@

Source:
@@ -964,7 +964,7 @@

Returns:

diff --git a/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html b/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html index b81e35b8..f73e4af9 100644 --- a/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html +++ b/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html @@ -575,7 +575,7 @@

(static, cons
diff --git a/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html b/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html index b14b7dd5..01d783b1 100644 --- a/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html +++ b/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html @@ -442,7 +442,7 @@
Parameters:
Source:
@@ -539,75 +539,7 @@

(s
Source:
- - - - - - - - - - - - - - - - -

data :Array

- - - - - - -
Type:
-
    -
  • - -Array - - -
  • -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
@@ -687,7 +619,7 @@

renderSource:
@@ -733,7 +665,7 @@

render
diff --git a/docs/api/module-LocusZoom_DataLayers-scatter.html b/docs/api/module-LocusZoom_DataLayers-scatter.html index c31f4665..76f7d137 100644 --- a/docs/api/module-LocusZoom_DataLayers-scatter.html +++ b/docs/api/module-LocusZoom_DataLayers-scatter.html @@ -1218,7 +1218,7 @@
Properties:

diff --git a/docs/api/module-LocusZoom_DataLayers.html b/docs/api/module-LocusZoom_DataLayers.html index 1bbe5e84..3e0afae1 100644 --- a/docs/api/module-LocusZoom_DataLayers.html +++ b/docs/api/module-LocusZoom_DataLayers.html @@ -415,6 +415,267 @@
Properties:
+

DataOperation

+ + + + +
+

A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.

+
+ + + +
Type:
+
    +
  • + +object + + +
  • +
+ + + + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
type + + +module:LocusZoom_DataFunctions +| + +'fetch' + + + + + + + +
from + + +Array.<String> + + + + + + <optional>
+ + + +

For operations of type "fetch", this is required. By default, it will fill in any items provided in "namespace" (everything specified in namespace triggers an adapter/network request) +A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace). +Eg, for ld to be fetched after association data, specify "ld(assoc)". Most LocusZoom examples fill in all items, in order to make the examples more clear.

name + + +String + + + + + + <optional>
+ + + +

The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation. +Eg, if the retrieved data is combined via several left joins in series: name: "assoc_plus_ld" -> feeds into {name: "final", requires: ["assoc_plus_ld"]}

requires + + +Array.<String> + + + + + + + +

The names of each adapter required. This does not need to specify dependencies, just +the names of other namespaces (or data operations) whose results must be available before a join can be performed

params + + +Array.<String> + + + + + + + +

Any user-defined parameters that should be passed to the particular join function +(see: module:LocusZoom_DataFunctions for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: params: ['assoc:position', 'ld:position2'] +Separate from this section, data functions will also receive a copy of "plot.state" automatically.

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + +

FilterOption

@@ -933,7 +1194,7 @@
Properties:
Source:
@@ -1163,7 +1424,7 @@
Properties:

diff --git a/docs/api/module-LocusZoom_Layouts.html b/docs/api/module-LocusZoom_Layouts.html index 325b030c..05769baa 100644 --- a/docs/api/module-LocusZoom_Layouts.html +++ b/docs/api/module-LocusZoom_Layouts.html @@ -200,7 +200,7 @@
Type:
Source:
@@ -273,7 +273,7 @@
Type:
Source:
@@ -345,7 +345,7 @@
Type:
Source:
@@ -424,7 +424,7 @@
Type:
Source:
@@ -503,7 +503,7 @@
Type:
Source:
@@ -583,7 +583,7 @@
Type:
Source:
@@ -655,7 +655,7 @@
Type:
Source:
@@ -727,7 +727,7 @@
Type:
Source:
@@ -799,7 +799,7 @@
Type:
Source:
@@ -878,7 +878,7 @@
Type:
Source:
@@ -957,7 +957,7 @@
Type:
Source:
@@ -1036,7 +1036,7 @@
Type:
Source:
@@ -1115,7 +1115,7 @@
Type:
Source:
@@ -1187,13 +1187,171 @@
Type:
Source:
+ + + + + + + +

+ + + + + + + + +

(inner, constant) bed_intervals :panel

+ + + + +
+

(extension) A panel containing an intervals data layer, eg for BED tracks. These field names match those returned by the LzParsers extension.

+
+ + + +
Type:
+
    +
  • + +panel + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + +
See:
+
+ +
+ + + +
+ + + + + + + + +

(inner, constant) bed_intervals_layer :data_layer

+ + + + +
+

(extension) A data layer with some preconfigured options for intervals display. This example was designed for standard BED3+ files and the field names emitted by the LzParsers extension.

+
+ + + +
Type:
+
    +
  • + +data_layer + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+
See:
+
+ +
+
@@ -1259,7 +1417,7 @@
Type:
Source:
@@ -1331,7 +1489,7 @@
Type:
Source:
@@ -1403,7 +1561,7 @@
Type:
Source:
@@ -1476,7 +1634,7 @@
Type:
Source:
@@ -1550,7 +1708,7 @@
Type:
Source:
@@ -1622,7 +1780,7 @@
Type:
Source:
@@ -1694,7 +1852,7 @@
Type:
Source:
@@ -1719,7 +1877,7 @@

(inner,

(extension) A plot layout that shows association summary statistics, genes, and interval data. This example assumes -chromHMM data. (see panel layout)

+chromHMM data. (see panel layout) Few people will use the full intervals plot layout directly outside of an example.

@@ -1767,7 +1925,7 @@
Type:
Source:
@@ -1798,7 +1956,7 @@

(inner, constant)
-

(extension) A panel containing an intervals data layer, eg for BED tracks

+

(extension) A panel containing an intervals data layer, eg for BED tracks. This is a legacy layout whose field names were specific to one partner site.

@@ -1846,7 +2004,7 @@
Type:
Source:
@@ -1928,7 +2086,7 @@
Type:
Source:
@@ -2087,7 +2245,7 @@
Type:
Source:
@@ -2198,7 +2356,8 @@

(inner, cons

(extension) A data layer with some preconfigured options for intervals display. This example was designed for chromHMM output, -in which various states are assigned numeric state IDs and (<= as many) text state names

+in which various states are assigned numeric state IDs and (<= as many) text state names.

+

This layout is deprecated; most usages would be better served by the bed_intervals_layer layout instead.

@@ -2246,7 +2405,7 @@
Type:
Source:
@@ -2326,7 +2485,7 @@
Type:
Source:
@@ -2398,7 +2557,7 @@
Type:
Source:
@@ -2470,7 +2629,7 @@
Type:
Source:
@@ -2542,7 +2701,7 @@
Type:
Source:
@@ -2614,7 +2773,7 @@
Type:
Source:
@@ -2686,7 +2845,7 @@
Type:
Source:
@@ -2758,7 +2917,7 @@
Type:
Source:
@@ -2831,7 +2990,7 @@
Type:
Source:
@@ -2903,7 +3062,7 @@
Type:
Source:
@@ -2982,7 +3141,7 @@
Type:
Source:
@@ -3054,7 +3213,7 @@
Type:
Source:
@@ -3126,7 +3285,7 @@
Type:
Source:
@@ -3160,7 +3319,7 @@
Type:

diff --git a/docs/api/module-LocusZoom_MatchFunctions.html b/docs/api/module-LocusZoom_MatchFunctions.html index 67fbd21b..99ea6d05 100644 --- a/docs/api/module-LocusZoom_MatchFunctions.html +++ b/docs/api/module-LocusZoom_MatchFunctions.html @@ -1532,7 +1532,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_ScaleFunctions.html b/docs/api/module-LocusZoom_ScaleFunctions.html index 1ab656db..54d8952f 100644 --- a/docs/api/module-LocusZoom_ScaleFunctions.html +++ b/docs/api/module-LocusZoom_ScaleFunctions.html @@ -389,7 +389,279 @@
Properties
-

(inner) if(parameters, input)

+

(inner) effect_direction(parameters, input) → {null}

+ + + + + + +
+

Calculate the effect direction based on beta, or the combination of beta and standard error. +Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
parameters + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
'+' + +

The value to return if the effect direction is positive

'-' + +

The value to return if the effect direction is positive

beta_field + +

The name of the field containing beta

stderr_beta_field + +

The name of the field containing stderr_beta

+ +
input + + +Object + + + +

This function should receive the entire datum object, rather than one single field

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +null + + +
+
+ + + + + + + + + + + + + +

(inner) if(parameters, value)

@@ -553,7 +825,7 @@
Properties
- input + value @@ -698,7 +970,7 @@

(inner) i
Source:
@@ -734,7 +1006,7 @@

(inner) i -

(inner) numerical_bin(parameters, input) → {*}

+

(inner) numerical_bin(parameters, value) → {*}

@@ -901,7 +1173,7 @@
Properties
- input + value @@ -1166,7 +1438,7 @@
Properties
Source:
@@ -1469,7 +1741,7 @@
Properties
Source:
@@ -1633,7 +1905,7 @@
Parameters:
Source:
@@ -1686,7 +1958,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_TransformationFunctions.html b/docs/api/module-LocusZoom_TransformationFunctions.html index 6bc4a96a..6efaa591 100644 --- a/docs/api/module-LocusZoom_TransformationFunctions.html +++ b/docs/api/module-LocusZoom_TransformationFunctions.html @@ -1257,7 +1257,7 @@
Returns:

diff --git a/docs/api/module-LocusZoom_Widgets-BaseWidget.html b/docs/api/module-LocusZoom_Widgets-BaseWidget.html index 6c1b59e4..df0486a4 100644 --- a/docs/api/module-LocusZoom_Widgets-BaseWidget.html +++ b/docs/api/module-LocusZoom_Widgets-BaseWidget.html @@ -1654,7 +1654,7 @@

update
diff --git a/docs/api/module-LocusZoom_Widgets-_Button.html b/docs/api/module-LocusZoom_Widgets-_Button.html index 8cbc308f..64fc2c28 100644 --- a/docs/api/module-LocusZoom_Widgets-_Button.html +++ b/docs/api/module-LocusZoom_Widgets-_Button.html @@ -3461,7 +3461,7 @@
Returns:

diff --git a/docs/api/module-LocusZoom_Widgets-display_options.html b/docs/api/module-LocusZoom_Widgets-display_options.html index 1e2ec999..6976e87d 100644 --- a/docs/api/module-LocusZoom_Widgets-display_options.html +++ b/docs/api/module-LocusZoom_Widgets-display_options.html @@ -467,7 +467,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Widgets-download_png.html b/docs/api/module-LocusZoom_Widgets-download_png.html index 8d8f947a..3f131348 100644 --- a/docs/api/module-LocusZoom_Widgets-download_png.html +++ b/docs/api/module-LocusZoom_Widgets-download_png.html @@ -370,7 +370,7 @@

Extends


diff --git a/docs/api/module-LocusZoom_Widgets-download_svg.html b/docs/api/module-LocusZoom_Widgets-download_svg.html index ed62c8f3..09ac6f50 100644 --- a/docs/api/module-LocusZoom_Widgets-download_svg.html +++ b/docs/api/module-LocusZoom_Widgets-download_svg.html @@ -459,7 +459,7 @@
Returns:

diff --git a/docs/api/module-LocusZoom_Widgets-filter_field.html b/docs/api/module-LocusZoom_Widgets-filter_field.html index 75c08653..0535635f 100644 --- a/docs/api/module-LocusZoom_Widgets-filter_field.html +++ b/docs/api/module-LocusZoom_Widgets-filter_field.html @@ -668,7 +668,7 @@
Fires:

diff --git a/docs/api/module-LocusZoom_Widgets-menu.html b/docs/api/module-LocusZoom_Widgets-menu.html index a3f1b6ad..90202e9c 100644 --- a/docs/api/module-LocusZoom_Widgets-menu.html +++ b/docs/api/module-LocusZoom_Widgets-menu.html @@ -253,7 +253,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Widgets-move_panel_down.html b/docs/api/module-LocusZoom_Widgets-move_panel_down.html index f4b2fa6c..a210ad22 100644 --- a/docs/api/module-LocusZoom_Widgets-move_panel_down.html +++ b/docs/api/module-LocusZoom_Widgets-move_panel_down.html @@ -164,7 +164,7 @@

new mo
diff --git a/docs/api/module-LocusZoom_Widgets-move_panel_up.html b/docs/api/module-LocusZoom_Widgets-move_panel_up.html index d2d91c35..0c4211a2 100644 --- a/docs/api/module-LocusZoom_Widgets-move_panel_up.html +++ b/docs/api/module-LocusZoom_Widgets-move_panel_up.html @@ -164,7 +164,7 @@

new move
diff --git a/docs/api/module-LocusZoom_Widgets-region_scale.html b/docs/api/module-LocusZoom_Widgets-region_scale.html index c9264f29..6df9a5a2 100644 --- a/docs/api/module-LocusZoom_Widgets-region_scale.html +++ b/docs/api/module-LocusZoom_Widgets-region_scale.html @@ -167,7 +167,7 @@

new regio
diff --git a/docs/api/module-LocusZoom_Widgets-remove_panel.html b/docs/api/module-LocusZoom_Widgets-remove_panel.html index c10a32eb..d7a742cc 100644 --- a/docs/api/module-LocusZoom_Widgets-remove_panel.html +++ b/docs/api/module-LocusZoom_Widgets-remove_panel.html @@ -233,7 +233,7 @@

Parameters:

diff --git a/docs/api/module-LocusZoom_Widgets-resize_to_data.html b/docs/api/module-LocusZoom_Widgets-resize_to_data.html index 78287773..4cca48cc 100644 --- a/docs/api/module-LocusZoom_Widgets-resize_to_data.html +++ b/docs/api/module-LocusZoom_Widgets-resize_to_data.html @@ -262,7 +262,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Widgets-set_state.html b/docs/api/module-LocusZoom_Widgets-set_state.html index 1f9f17f6..405a615c 100644 --- a/docs/api/module-LocusZoom_Widgets-set_state.html +++ b/docs/api/module-LocusZoom_Widgets-set_state.html @@ -416,7 +416,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Widgets-shift_region.html b/docs/api/module-LocusZoom_Widgets-shift_region.html index 7758b294..25643684 100644 --- a/docs/api/module-LocusZoom_Widgets-shift_region.html +++ b/docs/api/module-LocusZoom_Widgets-shift_region.html @@ -306,7 +306,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Widgets-title.html b/docs/api/module-LocusZoom_Widgets-title.html index 01a6a9b0..3c987a6b 100644 --- a/docs/api/module-LocusZoom_Widgets-title.html +++ b/docs/api/module-LocusZoom_Widgets-title.html @@ -255,7 +255,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Widgets-toggle_legend.html b/docs/api/module-LocusZoom_Widgets-toggle_legend.html index b496bcfd..0a45e0df 100644 --- a/docs/api/module-LocusZoom_Widgets-toggle_legend.html +++ b/docs/api/module-LocusZoom_Widgets-toggle_legend.html @@ -163,7 +163,7 @@

new togg
diff --git a/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html b/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html index 578ae799..06a8ee72 100644 --- a/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html +++ b/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html @@ -144,7 +144,7 @@

Parameters:
Source:
@@ -215,7 +215,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Widgets-zoom_region.html b/docs/api/module-LocusZoom_Widgets-zoom_region.html index 3fb14c1b..03769964 100644 --- a/docs/api/module-LocusZoom_Widgets-zoom_region.html +++ b/docs/api/module-LocusZoom_Widgets-zoom_region.html @@ -306,7 +306,7 @@
Parameters:

diff --git a/docs/api/module-LocusZoom_Widgets.html b/docs/api/module-LocusZoom_Widgets.html index e67f8452..02ce5c80 100644 --- a/docs/api/module-LocusZoom_Widgets.html +++ b/docs/api/module-LocusZoom_Widgets.html @@ -1240,7 +1240,7 @@
Properties:

diff --git a/docs/api/module-components_legend-Legend.html b/docs/api/module-components_legend-Legend.html index d3d94cf6..c7bc912f 100644 --- a/docs/api/module-components_legend-Legend.html +++ b/docs/api/module-components_legend-Legend.html @@ -1148,7 +1148,7 @@

showHome

Modules

Classes

Events

Global

+

Home

Modules

Classes

Events

Global


diff --git a/docs/api/module-data_requester-DataOperation.html b/docs/api/module-data_requester-DataOperation.html new file mode 100644 index 00000000..b8dff6e0 --- /dev/null +++ b/docs/api/module-data_requester-DataOperation.html @@ -0,0 +1,255 @@ + + + + + JSDoc: Class: DataOperation + + + + + + + + + + +
+ +

Class: DataOperation

+ + + + + + +
+ +
+ +

DataOperation(join_type, initiator, params)

+ + +
+ +
+
+ + + + + + +

new DataOperation(join_type, initiator, params)

+ + + + + + +
+

Perform a data operation (such as a join)

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
join_type + + +String + + + +
initiator + +

The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!

params + +

Optional user/layout parameters to be passed to the data function

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/api/module-ext_lz-credible-sets.html b/docs/api/module-ext_lz-credible-sets.html index e0c69683..428821f1 100644 --- a/docs/api/module-ext_lz-credible-sets.html +++ b/docs/api/module-ext_lz-credible-sets.html @@ -55,7 +55,6 @@

Loading and usage

The page must incorporate and load all libraries before this file can be used, including:

  • LocusZoom
  • -
  • gwas-credible-sets (available via NPM or a related CDN)

To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):

<script src="https://cdn.jsdelivr.net/npm/locuszoom@INSERT_VERSION_HERE/dist/ext/lz-credible-sets.min.js" type="application/javascript"></script>
@@ -176,7 +175,7 @@ 

Loading and usage


diff --git a/docs/api/module-ext_lz-dynamic-urls.html b/docs/api/module-ext_lz-dynamic-urls.html index b7304c25..ad7bbf25 100644 --- a/docs/api/module-ext_lz-dynamic-urls.html +++ b/docs/api/module-ext_lz-dynamic-urls.html @@ -877,7 +877,7 @@
Returns:

diff --git a/docs/api/module-ext_lz-forest-track.html b/docs/api/module-ext_lz-forest-track.html index ba9a732f..c22d8014 100644 --- a/docs/api/module-ext_lz-forest-track.html +++ b/docs/api/module-ext_lz-forest-track.html @@ -170,7 +170,7 @@

Loading and usage


diff --git a/docs/api/module-ext_lz-intervals-enrichment.html b/docs/api/module-ext_lz-intervals-enrichment.html index cf47e9a0..ddfcb06c 100644 --- a/docs/api/module-ext_lz-intervals-enrichment.html +++ b/docs/api/module-ext_lz-intervals-enrichment.html @@ -173,7 +173,7 @@

Loading and usage


diff --git a/docs/api/module-ext_lz-intervals-track.html b/docs/api/module-ext_lz-intervals-track.html index e8008e17..3663a1ac 100644 --- a/docs/api/module-ext_lz-intervals-track.html +++ b/docs/api/module-ext_lz-intervals-track.html @@ -47,8 +47,10 @@

Features provided

  • module:LocusZoom_ScaleFunctions~to_rgb
  • module:LocusZoom_DataLayers~intervals
  • module:LocusZoom_Layouts~standard_intervals
  • +
  • module:LocusZoom_Layouts~bed_intervals_layer
  • module:LocusZoom_Layouts~intervals_layer
  • module:LocusZoom_Layouts~intervals
  • +
  • module:LocusZoom_Layouts~bed_intervals
  • module:LocusZoom_Layouts~interval_association
  • Loading and usage

    @@ -176,7 +178,7 @@

    Loading and usage


    diff --git a/docs/api/module-ext_lz-parsers.html b/docs/api/module-ext_lz-parsers.html new file mode 100644 index 00000000..305ae970 --- /dev/null +++ b/docs/api/module-ext_lz-parsers.html @@ -0,0 +1,1280 @@ + + + + + JSDoc: Module: ext/lz-parsers + + + + + + + + + + +
    + +

    Module: ext/lz-parsers

    + + + + + + +
    + +
    + + + + + +
    + +
    +
    + + +

    Optional LocusZoom extension: must be included separately, and after LocusZoom has been loaded

    +

    This plugin exports helper function, as well as a few optional extra helpers for rendering the plot. The GWAS parsers can be used without registering the plugin.

    +

    To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):

    +
    <script src="https://cdn.jsdelivr.net/npm/locuszoom@INSERT_VERSION_HERE/dist/ext/lz-parsers.min.js" type="application/javascript"></script>
    +
    +

    To use with ES6 modules, import the helper functions and use them with your layout:

    +
    import { install, makeGWASParser, makeBed12Parser, makePlinkLDParser } from 'locuszoom/esm/ext/lz-parsers';
    +LocusZoom.use(install);
    +
    +

    Features provided

    +
    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +

    Methods

    + + + + + + + +

    (inner) makeBed12Parser(options)

    + + + + + + +
    +

    Parse a BED file, according to the widely used UCSC (quasi-)specification

    +

    NOTE: This original version is aimed at tabix region queries, and carries an implicit assumption that data is the +only thing that will be parsed. It makes no attempt to identify or handle header rows / metadata fields.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +object + + + + +
    Properties
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    normalize + + +Boolean + + + +

    Whether to normalize the output to the format expected by LocusZoom (eg type coercion +for numbers, removing chr chromosome prefixes, and using 1-based and inclusive coordinates instead of 0-based disjoint intervals)

    + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    function A configured parser function that runs on one line of text from an input file

    +
    + + + + + + + + + + + + + + + +

    (inner) makeGWASParser(options) → {function}

    + + + + + + +
    +

    Specify how to parse a GWAS file, given certain column information. +Outputs an object with fields in portal API format.

    +

    All column options must be provided as 1-indexed column IDs (human-friendly argument values)

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +
    Properties
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    marker_col + + + + <optional>
    + + + + + +
    + +

    A single identifier that specifies all of chrom, pos, ref, and alt as a single string field. Eg 1:23_A/C

    chrom_col + + + + <optional>
    + + + + + +
    + +

    Chromosome

    pos_col + + + + <optional>
    + + + + + +
    + +

    Position

    ref_col + + + + <optional>
    + + + + + +
    + +

    Reference allele (relative to human reference genome, eg GRCh37 or 38).

    alt_col + + + + <optional>
    + + + + + +
    + +

    Alt allele. Some programs specify generic A1/A2 instead; it is the job of the user to identify which columns of this GWAS are ref and alt.

    rsid_col + + + + <optional>
    + + + + + +
    + +

    rsID

    pvalue_col + + + + + + + + + +

    p-value (or -log10p)

    beta_col + + + + <optional>
    + + + + + +
    + +
    stderr_beta_col + + + + <optional>
    + + + + + +
    + +
    allele_freq_col + + + + <optional>
    + + + + + +
    + +

    Specify allele frequencies directly

    allele_count_col + + + + <optional>
    + + + + + +
    + +

    Specify allele frequencies in terms of count and n_samples

    n_samples_col + + + + <optional>
    + + + + + +
    + +
    is_alt_effect + + + + <optional>
    + + + + + +
    + + true + +

    Some programs specify beta and frequency information in terms of ref, others alt. Identify effect allele to orient values to the correct allele.

    is_neg_log_pvalue + + + + <optional>
    + + + + + +
    + + false + +
    delimiter + + + + <optional>
    + + + + + +
    + + '\t' + +

    Since this parser is usually used with tabix data, this is rarely changed (tabix does not accept other delimiters)

    + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    A parser function that can be called on each line of text with the provided options

    +
    + + + +
    +
    + Type +
    +
    + +function + + +
    +
    + + + + + + + + + + + + + +

    (inner) makePlinkLdParser(options) → {function}

    + + + + + + +
    +

    Parse the output of plink v1.9 R2 calculations relative to one (or a few) target SNPs. +See: https://www.cog-genomics.org/plink/1.9/ld and +reformatting commands at https://www.cog-genomics.org/plink/1.9/other

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +object + + + + +
    Properties
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    normalize + + +boolean + + + + + + <optional>
    + + + + + +
    + + true + +

    Normalize fields to expected datatypes and format; if false, returns raw strings as given in the file

    + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    A configured parser function that runs on one line of text from an input file

    +
    + + + +
    +
    + Type +
    +
    + +function + + +
    +
    + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + +
    + + + + + + + \ No newline at end of file diff --git a/docs/api/module-ext_lz-tabix-source.html b/docs/api/module-ext_lz-tabix-source.html index 212c8fca..48f9b466 100644 --- a/docs/api/module-ext_lz-tabix-source.html +++ b/docs/api/module-ext_lz-tabix-source.html @@ -49,7 +49,6 @@

    Loading and usage

    • Vendor assets
    • LocusZoom
    • -
    • tabix-reader (available via NPM or a related CDN)

    To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):

    <script src="https://cdn.jsdelivr.net/npm/locuszoom@INSERT_VERSION_HERE/dist/ext/lz-tabix-source.min.js" type="application/javascript"></script>
    @@ -66,7 +65,7 @@ 

    Loading and usage

    // Tabix performs region queries. If you are fetching interval data (one end outside the edge of the plot), then // "overfetching" can help to ensure that data partially outside the view region is retrieved // If you are fetching single-point data like association summary stats, then overfetching is unnecessary - params: { overfetch: 0.25 } + overfetch: 0.25 }]);
    @@ -180,7 +179,7 @@

    Loading and usage


    diff --git a/docs/api/module-ext_lz-widget-addons-covariates_model.html b/docs/api/module-ext_lz-widget-addons-covariates_model.html index 87a031ea..a79ab740 100644 --- a/docs/api/module-ext_lz-widget-addons-covariates_model.html +++ b/docs/api/module-ext_lz-widget-addons-covariates_model.html @@ -287,7 +287,7 @@
    Properties

    diff --git a/docs/api/module-ext_lz-widget-addons-data_layers.html b/docs/api/module-ext_lz-widget-addons-data_layers.html index ee2d84e9..bddaf6c5 100644 --- a/docs/api/module-ext_lz-widget-addons-data_layers.html +++ b/docs/api/module-ext_lz-widget-addons-data_layers.html @@ -163,7 +163,7 @@

    new data_l
    diff --git a/docs/api/module-ext_lz-widget-addons.html b/docs/api/module-ext_lz-widget-addons.html index 133e0110..e50a61a2 100644 --- a/docs/api/module-ext_lz-widget-addons.html +++ b/docs/api/module-ext_lz-widget-addons.html @@ -182,7 +182,7 @@

    Classes


    diff --git a/docs/api/module-registry_base-RegistryBase.html b/docs/api/module-registry_base-RegistryBase.html index d1cdb969..977aaa5a 100644 --- a/docs/api/module-registry_base-RegistryBase.html +++ b/docs/api/module-registry_base-RegistryBase.html @@ -987,7 +987,7 @@
    Returns:

    diff --git a/docs/api/registry_adapters.js.html b/docs/api/registry_adapters.js.html index 1b827204..9eebd64e 100644 --- a/docs/api/registry_adapters.js.html +++ b/docs/api/registry_adapters.js.html @@ -80,7 +80,7 @@

    Source: registry/adapters.js


    diff --git a/docs/api/registry_base.js.html b/docs/api/registry_base.js.html index 92da981f..bfe2c423 100644 --- a/docs/api/registry_base.js.html +++ b/docs/api/registry_base.js.html @@ -161,7 +161,7 @@

    Source: registry/base.js


    diff --git a/docs/api/registry_data_layers.js.html b/docs/api/registry_data_layers.js.html index 6c081a5a..df124d92 100644 --- a/docs/api/registry_data_layers.js.html +++ b/docs/api/registry_data_layers.js.html @@ -55,7 +55,7 @@

    Source: registry/data_layers.js


    diff --git a/docs/api/registry_data_ops.js.html b/docs/api/registry_data_ops.js.html new file mode 100644 index 00000000..19134e73 --- /dev/null +++ b/docs/api/registry_data_ops.js.html @@ -0,0 +1,224 @@ + + + + + JSDoc: Source: registry/data_ops.js + + + + + + + + + + +
    + +

    Source: registry/data_ops.js

    + + + + + + +
    +
    +
    /**
    + * "Data operation" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results
    + *
    + * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation
    + *  is a "join", such as combining association + LD together into a single set of records for plotting. Several join
    + *  functions (that operate by analogy to SQL) are provided built-in.
    + *
    + * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):
    + * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.
    + *   (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,
    + *    this is the recommended path to do so)
    + * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),
    + *    a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,
    + *    for datasets where the categories are not known before first render, this could generate automatic x-axis ticks
    + *    (PheWAS), automatic panel legends or color schemes (BED tracks), etc.
    + *
    + * Usually, a data operation receives two recordsets (the left and right members of the join, like "assoc" and "ld").
    + * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network
    + *   requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is
    + *   uncommon. (if possible, try to provide your data with fewer adapters/network requests!)
    + *
    + * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,
    + *   particularly for advanced features, may carry assumptions about field names/ formatting.
    + *   (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,
    + *   or it may match two datasets based on a specific way of identifying the variant)
    + *
    + * @module LocusZoom_DataFunctions
    + */
    +import {joins} from 'undercomplicate';
    +
    +import {RegistryBase} from './base';
    +
    +/**
    + * A plugin registry that allows plots to use both pre-defined and user-provided "data join" functions.
    + * @alias module:LocusZoom~DataFunctions
    + * @type {module:registry/base~RegistryBase}
    + */
    +const registry = new RegistryBase();
    +
    +function _wrap_join(handle) {
    +    // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).
    +
    +    // Many of our join functions are implemented with a different number of arguments than what a datafunction
    +    //   actually receives. (eg, a join function is generic and doesn't care about "context" information like plot.state)
    +    // This wrapper is simple shared code to handle required validation and conversion stuff.
    +    return (context, deps, ...params) => {
    +        if (deps.length !== 2) {
    +            throw new Error('Join functions must receive exactly two recordsets');
    +        }
    +        return handle(...deps, ...params);
    +    };
    +}
    +
    +// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to
    +//  pick the most significant claim in the catalog for a variant, rather than joining every possible match.
    +// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.
    +function assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {
    +    if (!assoc_data.length) {
    +        return assoc_data;
    +    }
    +
    +    // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each
    +    const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);
    +
    +    const catalog_flat = [];  // Store only the top significant claim for each catalog variant entry
    +    for (let claims of catalog_by_variant.values()) {
    +        // Find max item within this set of claims, push that to catalog_
    +        let best = 0;
    +        let best_variant;
    +        for (let item of claims) {
    +            const val = item[catalog_logp_name];
    +            if ( val >= best) {
    +                best_variant = item;
    +                best = val;
    +            }
    +        }
    +        best_variant.n_catalog_matches = claims.length;
    +        catalog_flat.push(best_variant);
    +    }
    +    return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);
    +}
    +
    +// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.
    +function genes_to_gnomad_constraint(genes_data, constraint_data) {
    +    genes_data.forEach(function(gene) {
    +        // Find payload keys that match gene names in this response
    +        const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;  // aliases are modified gene names
    +        const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene
    +        if (constraint) {
    +            // Add all fields from constraint data- do not override fields present in the gene source
    +            Object.keys(constraint).forEach(function (key) {
    +                let val = constraint[key];
    +                if (typeof gene[key] === 'undefined') {
    +                    if (typeof val == 'number' && val.toString().includes('.')) {
    +                        val = parseFloat(val.toFixed(2));
    +                    }
    +                    gene[key] = val;   // These two sources are both designed to bypass namespacing
    +                }
    +            });
    +        }
    +    });
    +    return genes_data;
    +}
    +
    +
    +/**
    + * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical
    + *
    + * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset
    + *
    + * @function
    + * @name left_match
    + * @param {Object} plot_state
    + * @param {Array[]} recordsets
    + * @param {String} left_key
    + * @params {String} right_key
    + */
    +registry.add('left_match', _wrap_join(joins.left_match));
    +
    +/**
    + * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical
    + *
    + * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.
    + *
    + * @function
    + * @name inner_match
    + * @param {Object} plot_state
    + * @param {Array[]} recordsets
    + * @param {String} left_key
    + * @params {String} right_key
    + */
    +registry.add('inner_match', _wrap_join(joins.inner_match));
    +
    +/**
    + * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical
    + *
    + * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.
    + *
    + * @function
    + * @name full_outer_match
    + * @param {Object} plot_state
    + * @param {Array[]} recordsets
    + * @param {String} left_key
    + * @params {String} right_key
    + */
    +registry.add('full_outer_match', _wrap_join(joins.full_outer_match));
    +
    +/**
    + * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.
    + *
    + * @function
    + * @name assoc_to_gwas_catalog
    + * @param {Object} plot_state
    + * @param {Array[]} recordsets An array with two items: assoc records, then catalog records
    + * @param {String} assoc_key The name of the key field in association data, eg variant ID
    + * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID
    + * @param {String} catalog_log_p_name The name of the "log_pvalue" field in gwas catalog data, used to choose the most significant claim for a given variant
    + */
    +registry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));
    +
    +/**
    + * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).
    + *
    + * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.
    + *
    + * @function
    + * @name genes_to_gnomad_constraint
    + * @param {Object} plot_state
    + * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data
    + */
    +registry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));
    +
    +export default registry;
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/api/registry_layouts.js.html b/docs/api/registry_layouts.js.html index 04f92fde..47487de5 100644 --- a/docs/api/registry_layouts.js.html +++ b/docs/api/registry_layouts.js.html @@ -27,7 +27,7 @@

    Source: registry/layouts.js

    import {RegistryBase} from './base';
    -import {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField} from '../helpers/layouts';
    +import {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';
     import * as layouts from '../layouts';
     
     /**
    @@ -47,26 +47,23 @@ 

    Source: registry/layouts.js

    throw new Error('Must specify both the type and name for the layout desired. See .list() for available options'); } // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as - // applying overrides or using namespaces to convert an abstract layout into a concrete one. + // applying overrides or applying namespaces. let base = super.get(type).get(name); - base = merge(overrides, base); - if (base.unnamespaced) { - delete base.unnamespaced; - return deepCopy(base); - } - let default_namespace = ''; - if (typeof base.namespace == 'string') { - default_namespace = base.namespace; - } else if (typeof base.namespace == 'object' && Object.keys(base.namespace).length) { - if (typeof base.namespace.default != 'undefined') { - default_namespace = base.namespace.default; - } else { - default_namespace = base.namespace[Object.keys(base.namespace)[0]].toString(); - } + + // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides. + // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced) + const custom_namespaces = overrides.namespace; + if (!base.namespace) { + // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout + // NOTE: The "merge namespace" behavior means that data layers can add new data easily, but this method + // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately). + delete overrides.namespace; } - default_namespace += default_namespace.length ? ':' : ''; - const result = applyNamespaces(base, base.namespace, default_namespace); + let result = merge(overrides, base); + if (custom_namespaces) { + result = applyNamespaces(result, custom_namespaces); + } return deepCopy(result); } @@ -91,6 +88,13 @@

    Source: registry/layouts.js

    } // Ensure that each use of a layout can be modified, by returning a copy is independent const copy = deepCopy(item); + + // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested + // from external sources. This is purely a hint, because not every layout is generated through the registry. + if (type === 'data_layer' && copy.namespace) { + copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort(); + } + return super.get(type).add(name, copy, override); } @@ -176,7 +180,7 @@

    Source: registry/layouts.js


    diff --git a/docs/api/registry_matchers.js.html b/docs/api/registry_matchers.js.html index 86252aad..440a1dbc 100644 --- a/docs/api/registry_matchers.js.html +++ b/docs/api/registry_matchers.js.html @@ -154,7 +154,7 @@

    Source: registry/matchers.js


    diff --git a/docs/api/registry_transforms.js.html b/docs/api/registry_transforms.js.html index df6d9fca..0260ab41 100644 --- a/docs/api/registry_transforms.js.html +++ b/docs/api/registry_transforms.js.html @@ -107,7 +107,7 @@

    Source: registry/transforms.js


    diff --git a/docs/api/registry_widgets.js.html b/docs/api/registry_widgets.js.html index f6ab585f..9589cf29 100644 --- a/docs/api/registry_widgets.js.html +++ b/docs/api/registry_widgets.js.html @@ -53,7 +53,7 @@

    Source: registry/widgets.js


    diff --git a/docs/guides/data_retrieval.html b/docs/guides/data_retrieval.html index 94501c73..eec8cd36 100644 --- a/docs/guides/data_retrieval.html +++ b/docs/guides/data_retrieval.html @@ -101,15 +101,21 @@

    Table of Contents

  • Example: Loading data from static JSON files
  • Mix and match
  • +
  • Every layer has its own, private, view of the data
  • What if my data doesn’t fit the expected format?
  • +
  • Combining two kinds of data in one place +
  • Creating your own custom adapter
  • See also
  • @@ -122,7 +128,7 @@

    Your first plot: defining

    Below is an example that defines how to retrieve the data for a “classic” LocusZoom plot, in which GWAS, LD, and recombination rate are overlaid on a scatter plot, with genes and gnomAD constraint information on another track below. In total, five REST API endpoints are used to create this plot: four standard datasets, and one user-provided summary statistics file.

    const apiBase = 'https://portaldev.sph.umich.edu/api/v1/';
     const data_sources = new LocusZoom.DataSources()
    -    .add('assoc', ['AssociationLZ', {url: apiBase + 'statistic/single/', params: { source: 45, id_field: 'variant' }}])
    +    .add('assoc', ['AssociationLZ', {url: apiBase + 'statistic/single/', source: 45, id_field: 'variant' }])
         .add('ld', ['LDServer', { url: 'https://portaldev.sph.umich.edu/ld/', source: '1000G', population: 'ALL', build: 'GRCh37' }])
         .add('recomb', ['RecombLZ', { url: apiBase + 'annotation/recomb/results/', build: 'GRCh37' }])
         .add('gene', ['GeneLZ', { url: apiBase + 'annotation/genes/', build: 'GRCh37' }])
    @@ -130,15 +136,15 @@ 

    Your first plot: defining

    Of course, defining datasets is only half of the process; see the Getting Started Guide for how to define rendering instructions (layout) and combine these pieces together to create the LocusZoom plot.

    Understanding the example

    In the example above, a new data source is added via a line of code such as the following:

    -
    data_sources.add('assoc', ['AssociationLZ', {url: apiBase + 'statistic/single/', params: { source: 45, id_field: 'variant' }}]);
    +
    data_sources.add('assoc', ['AssociationLZ', {url: apiBase + 'statistic/single/', source: 45, id_field: 'variant' }]);

    A lot is going on in this line!

    • data_sources.add defines a piece of information that could be used by the plot. (if no data layer asks for data from this source, then no API request will ever be made)
    • The first argument to the function is a namespace name. It is an arbitrary reference to this particular piece of data. For example, you might want to plot three association studies together in the same window, and they could be defined as .add('mystudy', ...), .add('somephenotype', ...), .add('founditinthegwascatalog', ...)
        -
      • In the layouts guide, we will see how data_layer.fields uses these namespaces to identify what data to render.
      • +
      • In the layouts guide, we will see how data layers use these namespaces.
    • -
    • The second argument to the function is a list of values: the name of a predefined adapter that defines how to retrieve this data, followed by an object of configuration options (like url and params) that control which data will be fetched. Each type of data has its own options; see the documentation for a guide to available choices. +
    • The second argument to the function is a list of values: the name of a predefined adapter that defines how to retrieve this data, followed by an object of configuration options (like url and source) that control which data will be fetched. Each type of data has its own options; see the documentation for a guide to available choices.
      • You are not limited to the types of data retrieval built into LocusZoom.js. See “creating your own adapter” for more information.
    • @@ -147,8 +153,8 @@

      The importance of genome build

      You may notice that in the example above, many of the datasets specify build: 'GRCh37. For “standard” datasets that are widely used (LD, genes, recombination, and GWAS catalog), the UMich APIs will automatically try to fetch the most up-to-date list of genes and GWAS catalog entries for the specified genome build. We currently support build GRCh37 and GRCh38. Be sure to use the genome build that matches your dataset.

      We periodically update our API server. If you think new information is missing, please let us know.

      What should the data look like?

      -

      In theory, LocusZoom.js can display whatever data it is given: layouts allow any individual layout to specify what fields should be used for the x and y axes.

      -

      In practice, it is much more convenient to use pre-existing layouts that solve a common problem well out of the box: the set of options needed to control point size, shape, color, and labels is rather verbose, and highly custom behaviors entail a degree of complexity that is not always beginner friendly. For basic LocusZoom.js visualizations, our default layouts assume that you use the field names and format conventions defined in the UM PortalDev API docs. This is the quickest way to get started.

      +

      In theory, LocusZoom.js can display whatever data it is given: layouts allow any individual layer to specify what fields should be used for the x and y axes.

      +

      In practice, it is much more convenient to use pre-existing layouts that solve a common problem well out of the box: the set of options needed to control point size, shape, color, and labels is rather verbose, and highly custom behaviors entail a degree of complexity that is not always beginner friendly. For basic LocusZoom.js visualizations, our default layouts assume that you use the field names and format conventions defined in the UM PortalDev API docs. This is the quickest way to get started. Most layouts in the provided LocusZoom.Layouts registry will tell you what fields are expected (via the _auto_fields property), and most plots will log a console error message if the response is missing expected information.

      Most users will only need to implement their own way of retrieving GWAS summary statistics; the other annotations are standard datasets and can be freely used from our public API. For complex plots (like annotations of new data), see our example gallery.

      How data gets to the plot

      If you are building a custom tool for exploring data, it is common to show the same data in several ways (eg, a LocusZoom plot next to a table of results). The user will have a better experience if the two widgets are synchronized to always show the same data, which raises a question: which widget is responsible for making the API request?

      @@ -173,6 +179,15 @@

      Example: Loading data from

      Mix and match

      Each data adapter in the chain is largely independent, and it is entirely normal to mix data from several sources: for example, GWAS data from a tabix file alongside genes data from the UMich API server.

      If a single data layer needs to combine two kinds of data (eg association and LD), you will achieve the best results if the sources have some common assumptions about data format. Adapters are highly modular, but because they do not enforce a specific contract of field names or payload structure, you are responsible for ensuring that the resulting data works with the assumptions of your layout.

      +

      Every layer has its own, private, view of the data

      +

      Each data layer in LocusZoom is intended to be independent of others. Thus, each layer must individually specify its own way to connect data together.

      +

      Likewise, each layer defines a “local” way of identifying where to find the data it needs. For example, consider a layer layout as follows:

      +
      {
      +  namespace: {'assoc': 'mystudy'},
      +  id_field: 'assoc:variant'
      +}
      +

      The namespace is specified as a key-value pair of local_name: global_data_source_name. This instruction says: “whenever this layer asks for something from assoc, make a request to an item named mystudy”. Every field in the layout refers to the local_name. This layer of indirection allows a layout to be used many times to plot different datasets, and only the namespace needs to be changed.

      +

      Any changes made to the data within one layer should not affect copies of the same data in other places. This property makes it easier to display the same data in two different ways, without having to make a duplicate network request.

      What if my data doesn’t fit the expected format?

      The built-in adapters are designed to work with a specific set of known REST APIs and fetch data over the web, but we provide mechanisms to customize every aspect of the data retrieval process, including how to construct the query sent to the server and how to modify the fields returned. See the guidance on “custom adapters” below.

      In general, the more similar that your field names are to those used in premade layouts, the easier it will be to get started with common tasks. Certain features require additional assumptions about field format, and these sorts of differences may cause behavioral (instead of cosmetic) issues. For example:

      @@ -181,91 +196,129 @@

      What if my data doesn’
    • JavaScript is not able to accurately represent very small pvalues (numbers smaller than ~ 5e-324), and will truncate them to 0, changing the meaning of your data. For this reason, we recommend sending your data to the web page already transformed to -log pvalue format; this is much less susceptible to problems with numerical underflow.

    If the only difference is field names, you can customize the layout to tell it where to find the required information. (see: guide to layouts and rendering for details) Transformation functions (like neglog10) can then be used to ensure that custom data is formatted in a way suitable for rendering and plotting.

    +

    Combining two kinds of data in one place

    +

    Sometimes, a single data layer will depend on information from two different sources (like an association plot colored by LD information).

    +

    Data operations (layout directive)

    +

    The act of combining two pieces of data is performed by data operations. In order to let the same piece of data be rendered in different ways, these instructions are placed within each data layer layout. The decision on how to combine two pieces of information is specified at the point where the data is used.

    +

    A sample layout directive for data operations would be as follows:

    +
    {
    +    namespace: { 'assoc': 'mystudy', 'ld': 'some_ld' },
    +    data_operations: [
    +        {
    +            type: 'fetch',
    +            from: ['assoc', 'ld(assoc)'],
    +        },
    +        {
    +            type: 'left_match',
    +            name: 'assoc_plus_ld',
    +            requires: ['assoc', 'ld'],
    +            // Tell the join function which field names to be used for the join. Each join function has its own possible parameters.
    +            params: ['assoc:position', 'ld:position2'], 
    +        },
    +    ]
    +}
    +

    Dependencies

    +

    The first instruction in the example above specifies how to retrieve the data: {type: 'fetch', from: ['assoc', 'ld(assoc)'}

    +

    A single “fetch” operation is used to specify all of the data retrieval. This specifies what information a data layer should retrieve. It refers to the “local” namespace name (like “assoc”), and tells locuszoom to make a request to the global datasource named “mystudy”.

    +

    Some adapters can not begin to define their request until they see the data from another adapter: eg LD information is retrieved relative to the most significant association variant in the region. This is a dependency, and one (or more) dependencies can be specified using the syntax some_request(first_dependency, nth_dependency). LocusZoom will automatically reorder and parallelize requests based on the dependencies given. If several requests each have no dependencies, they will be performed in parallel.

    +

    Each individual adapter will receive data it depends on as a function argument to getData. The string syntax reflects how the data is connected internally! In the example above, “ld” is only retrieved after a request to “assoc” is complete, and the LD adapter received the assoc data to help define the next request.

    +
    +

    NOTE: Sometimes it is useful to extend an existing layout to fetch additional kinds of data. It can be annoying to extend a “list” field like fetch.from, so LocusZoom has some magic to help out: if an item is specified as a source of data for this layer (eg namespace: {'assoc': 'assoc'}), it will automatically be added to the fetch.from instruction. This means that everything listed in data_layer.namespace will trigger a data retrieval request. You only need to modify the contents of fetch.from if you want to specify that the new item depends on something else, eg “don’t fetch LD until assoc data is ready: ld(assoc)”. All of the built in LZ layouts are verbose and explicit, in an effort to reduce the amount of “magic” and make the examples easier to understand.

    +
    +

    Joins (“Data functions”)

    +

    The second instruction in the example above is a join. It specifies how to compare multiple sets of data into one list of records (“one record per data point on the plot”).

    +

    Any function defined in LocusZoom.DataFunctions can be referenced. Several builtin joins are provided (including left_match, inner_match, and full_outer_match). Any arbitrary join function can be added to the LocusZoom.DataFunctions registry, with the call signature ({plot_state, data_layer}}, [recordsetA, recordsetB...], ...params) => combined_results.

    +

    Data operations are synchronous; they are used for data formatting and cleanup. Use adapters for asynchronous operations like network requests.

    +

    The plot will receive the last item in the dependency graph (taking into account both adapters and joins). If something looks strange, make sure that you have specified how to join all your data sources together.

    +
    +

    TIP: Because data functions are given access to plot.state and the data layer instance that initiated the request, they are able to do surprisingly powerful things, like filtering the returned data in response to a global plot_state parameter or auto-defining a layout (data_layer.layout) in response to dynamic data (axis labels, color scheme, etc). This is a very advanced usage and has the potential for side effects; use sparingly! See the LocusZoom unit tests for a few examples of what is possible.

    +

    Creating your own custom adapter

    +

    Can I reuse existing code?

    +

    The built-in LocusZoom adapters can be used as-is in many cases, so long as the returned payload matches the expected format. You may need to write your own custom code (adapter subclass) in the following scenarios:

    +
      +
    1. If an expression must be evaluated in order to retrieve data (eg constructing URLs based on query parameters, or LD requests where the reference variant is auto-selected from the most significant hit in a GWAS)
    2. +
    3. If the actual headers, body, or request method must be customized in order to carry out the request. This happens when data is retrieved from a particular technology (REST vs GraphQL vs Tabix), or if the request must incorporate some form of authentication credentials.
    4. +
    5. If some of the fields in your custom API format need to be transformed or renamed in order to match expectations in LZ code. For example, the LD adapter may try to suggest an LD reference variant by looking for the GWAS variant with the largest log_pvalue. (over time, built-in LZ adapters trend towards being more strict about field names; it is easier to write reliable code when not having to deal with unpredictable data!)
    6. +

    Re-using code via subclasses

    Most custom sites will only need to change very small things to work with their data. For example, if your REST API uses the same payload format as the UM PortalDev API, but a different way of constructing queries, you can change just one function and define a new data adapter:

    -
    const AssociationLZ = LocusZoom.Adapters.get('AssociationLZ');
    -class CustomAssociation extends AssociationLZ {
    -    getURL(state, chain, fields) {
    -        // The inputs to the function can be used to influence what query is constructed. Eg, the current view region is stored in `plot.state`.
    -        const {chr, start, end} = state;
    -        // Fetch the region of interest from a hypothetical REST API that uses query parameters to define the region query, for a given study URL such as `data.example/gwas/<id>/?chr=_&start=_&end=_`
    -        return `${this.url}/${this.params.study_id}/?chr=${encodeURIComponent(chr)}&start=${encodeURIComponent(start)}&end${encodeURIComponent(end)}`
    -  }
    -}
    -// A custom adapter should be added to the registry before using it
    -LocusZoom.Adapters.add('CustomAssociation', CustomAssociation);
    -
    -// From there, it can be used anywhere throughout LocusZoom, in the same way as any built-in adapter
    -data_sources.add('mystudy', ['CustomAssociation', {url: 'https://data.example/gwas', params: { study_id: 42 }}]);
    -

    In the above example, an HTTP GET request will be sent to the server every time that new data is requested. If further control is required (like sending a POST request with custom body), you may need to override additional methods such as fetchRequest. See below for more information, then consult the detailed developer documentation for details.

    +
    const AssociationLZ = LocusZoom.Adapters.get('AssociationLZ');
    +class CustomAssociation extends AssociationLZ {
    +    _getURL(request_options) {
    +        // Every adapter receives the info from plot.state, plus any additional request options calculated/added in the function `_buildRequestOptions`
    +      // The inputs to the function can be used to influence what query is constructed. Eg, since the current view region is stored in `plot.state`:
    +        const {chr, start, end} = request_options;
    +        // Fetch the region of interest from a hypothetical REST API that uses query parameters to define the region query, for a given study URL such as `data.example/gwas/<id>/?chr=_&start=_&end=_`
    +        return `${this._url}/${this.source}/?chr=${encodeURIComponent(chr)}&start=${encodeURIComponent(start)}&end${encodeURIComponent(end)}`
    +  }
    +}
    +// A custom adapter should be added to the registry before using it
    +LocusZoom.Adapters.add('CustomAssociation', CustomAssociation);
    +
    +// From there, it can be used anywhere throughout LocusZoom, in the same way as any built-in adapter
    +data_sources.add('mystudy', ['CustomAssociation', {url: 'https://data.example/gwas', source: 42 }]);
    +

    In the above example, an HTTP GET request will be sent to the server every time that new data is requested. If further control is required (like sending a POST request with custom body), you may need to override additional methods such as fetchRequest. See below for more information, then consult the detailed developer documentation for details.

    Common types of data retrieval that are most often customized:

    • GWAS summary statistics
        -
      • This fetches the data directly with minor cleanup. You can customize the built-in association adapter, or swap in another way of fetching the data (like tabix).
      • -
    • -
    • User-provided linkage disequilibrium (LD) -
        -
      • This contains special logic used to combine association data (from a previous request) with LD information. To ensure that the matching code works properly, we recommend matching the payload format of the public LDServer, but you can customize the getURL method to control where the data comes from.
      • -
      • For performance reasons, connecting LD and association data together assumes that both datasets are sorted in order of increasing chromosome and position.
      • +
      • This fetches the data directly with minor cleanup. You can customize the built-in association adapter, or swap in another way of fetching the data (like tabix). You may want to ensure that a field called log_pvalue is present, and possibly customize other fields as well.
    • -
    • PheWAS results
    • +
    • PheWAS results (not every server returns PheWAS results in the same format)

    What happens during a data request?

    The adapter performs many functions related to data retrieval: constructing the query, caching to avoid unnecessary network traffic, and parsing the data into a transformed representation suitable for use in rendering.

    Methods are provided to override all or part of the process, called in roughly the order below:

    -
    getData(state, fields, outnames, transformations)
    -    getRequest(state, chain, fields)
    -        getCacheKey(state, chain, fields)
    -        fetchRequest(state, chain, fields)
    -            getURL(state, chain, fields)
    -    parseResponse(resp, chain, fields, outnames, transformations)
    -        normalizeResponse(data)
    -        annotateData(data, chain)
    -        extractFields(data, fields, outnames, trans)
    -        combineChainBody(data, chain, fields, outnames)
    +
    getData(plot_state, ...dependent_data)
    +    _buildRequestOptions(plot_state, ...dependent_data)
    +    _getCacheKey(request_options)
    +    _performRequest(request_options)
    +        _getURL(request_options)
    +    // Parse JSON, convert columnar data to rows, etc
    +    _normalizeResponse(raw_response, options)
    +        cache.add(records)
    +    // User-specified cleanup of the parsed response fields. Can be used for things like field renaming, record filtering, or adding new calculated fields on the fly
    +    _annotateRecords(records, options)
    +    // Usually not overridden: this method can be used to apply prefixes (assoc:id, catalog:id, etc) and to simplify down verbose responses to just a few `limit_fields` of interest
    +    _postProcessResponse(records, options)

    The parameters passed to getData are as follows:

      -
    • state - this is the current “state” of the plot. This contains information about the current region in view (chr, start, and end), which is often valuable in querying a remote data source for the data in a given region.
    • -
    • fields - this is an array of field names that have been requested from this data source. Note that the “namespace:” part of the name has been removed in this array. Note: most data adapters will return only the fields that are requested by a data layer. Each data layer can request a different set of fields, and thus different parts of the plot may have a different view of the same data.
    • -
    • outnames - this is an array with length equal to fields with the original requested field name. This value contains the data source namespace. The data output for each field should be given the name in this array. This is rarely used directly.
    • -
    • transformations - this is an array with length equal to fields with the collection of value transformations functions specified by the user to be run on the returned field. This is rarely used directly.
    • +
    • plot_state: Every adapter request contains a copy of plot.state, which stores global information (like view region chr/start/end) that can be used to customize the request.
    • +
    • ...dependent_data: If the adapter depends on prior requests, the parsed records from each prior request will be passed to this function (each function argument would be one dependent request, represented as an array of row-based record objects: [{'assoc:variant': '1:23_A/C', 'assoc:log_pvalue': 75 }])
    -

    Step 1: Fetching data from a remote server

    -

    The first step of the process is to retrieve the data from an external location. getRequest is responsible for deciding whether the query can be satisfied by a previously cached request, and if not, sending the response to the server. At the conclusion of this step, we typically have a large unparsed string: eg REST APIs generally return JSON-formatted text, and tabix sources return lines of text for records in the region of interest.

    +

    This function will return row-format data representing the response for just that request. It will use a cached response if feasible. By default, LocusZoom will apply certain transformation to the returned response so that it is easier to use with a data layer (see adapter documentation for details). All fields in the original response will be returned as given.

    +

    Step 1: Fetching data from a remote server and converting to records

    +

    The first step of the process is to retrieve the data from an external location. _buildRequestOptions can be used to store any parameters that customize the request, including information from plot.state. If the response is cached, the cache value will be returned; otherwise, a request will be initiated. Once the request is complete, the result will be parsed and cached for future use.

    +

    At the conclusion of this step, we typically have an array of records, one object per row of data. The field names and contents are very close to what was returned by the API.

    Most custom data sources will focus on customizing two things:

      -
    • getURL (how to ask the external source for data)
    • -
    • getCacheKey (decide whether the request can be satisfied by local data) +
    • _getURL (how to ask the external source for data)
    • +
    • _getCacheKey (decide whether the request can be satisfied by local data)
      • By default this returns a string based on the region in view: '${state.chr}_${state.start}_${state.end}'
      • You may need to customize this if your source has other inputs required to uniquely define the query (like LD reference variant, or calculation parameters for credible set annotation).
    -

    Step 2: Formatting and parsing the data

    -

    The parseResponse sequence handles the job of parsing the data. It can be used to convert many different API formats into a single standard form. There are four steps to the process:

    +

    Step 2: Transforming data into a form usable by LocusZoom

    +

    In a prior step, the records were normalized, but kept in a form that is usually close to what the API returned. In this step, records are modified for use in the plot:

      -
    • normalizeResponse - Converts any data source response into a standard format. This can be used when you want to take advantage of existing data handling functionality of a particular adapter (like performing an interesting calculation), but your data comes from something like a tabix file that needs to be adjusted to match the expected format. +
    • _normalizeResponse - Converts any data source response into a standard format. This can be used when you want to take advantage of existing data handling functionality of a particular adapter (like performing an interesting calculation), but your data comes from something like a tabix file that needs to be adjusted to match the expected format.
      • Internally, most data layer rendering types assume that data is an array, with each datum element represented by an object: [{a_field: 1, other_field: 1}]
      • Some sources, such as the UM PortalDev API, represent the data in a column-oriented format instead. ({a_field: [1], other_field: [1]}) The default adapter will attempt to detect this and automatically transform those columns into the row-based one-record-per-datum format.
    • -
    • annotateData - This can be used to add custom calculated fields to the data. For example, if your data source does not provide a variant marker field, one can be generated in javascript (by concatenating chromosome:position_ref/alt), without having to modify the web server.
    • -
    • extractFields - Each data layer receives only the fields it asks for, and the data is reformatted in a way that clearly identifies where they come from (the namespace is prefixed onto each field name, eg {'mynamespace:a_field': 1}). +
    • _annotateRecords - This can be used to modify the data returned in many useful ways: filtering, renaming fields, or adding new calculated fields. Example scenarios:
        -
      • The most common reason to override this method is if the data uses an extremely complex payload format (like genes), and a custom data layer expects to receive that entire structure as-is. If you are working with layouts, the most common sign of an adapter that does this is that the data layer asks for a nonexistent field (gene:all - a synthetic value whose sole purpose is to indicate that the source is used)
      • +
      • A field called pValue can be transformed into one called log_pvalue
      • +
      • If a tabix file returns two categories of data at once, then the adapter could decide which to show according to a user-selected option stored in plot.state (So long as both datasets are not too big, this trick works well with the set_state toolbar widget: it provides a cheap way to toggle between datasets, without requiring an extra trip to the server!)
      • +
      • Adding custom calculated fields to the data. For example, if your data source does not provide a variant marker field, one can be generated in javascript (by concatenating chromosome:position_ref/alt), without having to modify the web server.
    • -
    • combineChainBody: If a single data layer asks for data from more than one source, this function is responsible for combining several different pieces of information together. For example, in order to show an association plot with points colored by LD, the LD adapter implements custom code that annotates the association data with matching LD information. At the end of this function, the data layer will receive a single combined record per visualized data element.
    • -
    -

    Working with the data “chain”

    -

    Each data layer is able to request data from multiple different sources. Internally, this process is referred to as the “chain” of linked data requested. LocusZoom.js assumes that every data layer is independent and decoupled: it follows that each data layer has its own chain of requests and its own parsing process.

    -

    This chain defines how to share information between different adapters. It contains of two key pieces:

    +
  • _postProcessResponse: Most people don’t customize this method. After all calculations are done, this is used to transform the data into a format useful by LocusZoom. Eg, field names are prefixed to reflect where the data came from, like assoc:log_pvalue. This happens last, so most custom code that modifies returned records doesn’t have to deal with it.
      -
    • body - the actual consolidated payload. Each subsequent link in the chain receives all the data from the previous step as chain.body
    • -
    • headers - this is a “meta” section used to store information used during the consolidation process. For example, the LD adapter needs to find the most significant variant from the previous step in the chain (association data) in order to query the API for LD. The name of that variant can be stored for subsequent use during the data retrieval process.
    • +
    • Most of the behaviors in this method can be overridden using adapter options, without writing custom code. (see documentation for details)
    • +
  • -

    Only chain.body is sent to the data layer. All other parts of the chain are discarded at the end of the data retrieval process.

    See also

    • LocusZoom.js is able to share its data with external widgets on the page, via event listeners that allow those widgets to update whenever the user interacts with the plot (eg panning or zooming to change the region in view). See subscribeToData in the guide to interactivity for more information.
      diff --git a/docs/guides/interactivity.html b/docs/guides/interactivity.html index 1ace033a..57d3c01c 100644 --- a/docs/guides/interactivity.html +++ b/docs/guides/interactivity.html @@ -108,7 +108,6 @@

      Table of Contents

    • Events communicate with the outside world
    • Share data with other widgets via subscribeToData
    • Filters control what is shown @@ -183,7 +182,6 @@

      Layout mutations change what

      Each LocusZoom rendering is controlled by a declarative set of layout options. In practice, this means that a clever developer can change key options (eg point color, or what field is shown on the y-axis) simply by modifying (mutating) the layout, then re-rendering.

      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:

        -
      • 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.
      • 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.
      • 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).
      @@ -241,36 +239,14 @@

      Events communicate with the o

      Share data with other widgets via subscribeToData

      Using the same mechanisms and syntax as an LZ data layer, let arbitrary parts of the page listen for new data by asking for the (namespaced) fields of interest.

      Sample usage:

      -
      const fields = ['aggregation:all', 'gene:all'];
      -const success_callback = (data) => console.log(data);
      -const opts = { onerror: (err) => console.log(err) };
      -
      -plot.subscribeToData(fields, success_callback, opts);
      +
      // Receives exactly the same data as the specified datalayer ID (avoids having to duplicate namespace and data operations code)
      +const spec = { from_layer: 'panel1.layer1' };
      +const success_callback = (data) => console.log(data);
      +const opts = { onerror: (err) => console.log(err) };
      +
      +plot.subscribeToData(spec, success_callback, opts);

      When data is received, calls success_callback with the resulting data (an array of objects, with each object representing one datapoint). subscribeToData can be used to draw tables or companion visualizations on the page, separate from LocusZoom but automatically updating to stay in sync as the user clicks or pans across the plot. Specify onerror as an option to provide an error handling callback.

      -

      Caveat: namespaces

      -

      Note that the returned payload will contain namespaced fields: the data will appear in the same format as used internally by LocusZoom. Sometimes this is annoying because it means that any external widget using the data will need to account for LocusZoom’s namespacing syntax.

      -

      If you don’t want to be bothered with namespacing, the following utility function may be useful:

      -
      /**
      -* Remove the `sourcename:` prefix from field names in the data returned by an LZ datasource
      -*
      -* This is a convenience method for writing external widgets (like tables) that subscribe to the
      -* plot; typically we don't want to have to redefine the table layout every time someone selects
      -* a different association study.
      -* As with all convenience methods, it has limits: don't use it if the same field name is requested
      -* from two different sources!
      -* @param {Object} data An object representing the fields for one row of data
      -* @param {String} [prefer] Sometimes, two sources provide a field with same name. Specify which
      -* source will take precedence in the event of a conflict.
      -*/
      -function deNamespace(data, prefer) {
      -    return Object.keys(data).reduce((acc, key) => {
      -        const new_key = key.replace(/.*?:/, '');
      -        if (!Object.prototype.hasOwnProperty.call(acc, new_key) || (!prefer || key.startsWith(prefer))) {
      -            acc[new_key] = data[key];
      -        }
      -        return acc;
      -    }, {});
      -}
      +

      Sometimes you want exact control over what data is retrieved, rather than mirroring a data layer. In that case, replace from_layer with two options (namespace and data_operations) to manually specify how data is retrieved for this callback. See data layer documentation for syntax and usage.

      Advanced alternative

      Sometimes, the existing page already has several widgets sharing data, and it would be difficult to rewrite things after the fact in a way that ceded control of data to LocusZoom. In this case, some compromise needs to be reached: how can LocusZoom fetch what it needs (possibly rendering only a subset of the data available), without duplicating API calls to the server that have already been made elsewhere?

      Reactive rendering frameworks sometimes solve the problem of sharing mutable data via a shared local cache (store) that represents data from the server. LocusZoom can then make requests to that store, and the store is then responsible for deciding whether a new server request is needed. This allows the result of a single API request to power all widgets on the page without redundant network traffic. The store call can then return a promise representing either local data, or the result of a server request, as appropriate.

      @@ -279,27 +255,27 @@

      Advanced alternative

      Filters control what is shown

      Filters can be used to control what elements are shown on a panel. This will hide elements, but preserve the space that those elements would have occupied: eg, the axis limits will reflect all of the data, not just what is currently shown.

      Filters are specified in data layout layouts, and can be applied to most data layer types.

      -
      {
      -  ...options,
      -  filters: [
      -    { field: '{{namespace[access]}}score', operator: '!=', value: null },
      -  ],
      -}
      +
      {
      +  ...options,
      +  filters: [
      +    { field: '{{namespace[access]}}score', operator: '!=', value: null },
      +  ],
      +}

      Scatter plots have an additional option to show labels for some or all data elements. This is controlled via adding a similar filter block inside label:

      -
      {
      -  id: 'phewaspvalues',
      -  ...options,
      -  label: {
      -    text: '{{{{namespace[phewas]}}trait_label}}',
      -    filters: [
      -      {
      -        field: '{{namespace[phewas]}}log_pvalue',
      -        operator: '>=',
      -        value: 20,
      -      },
      -    ],
      -  }
      -}
      +
      {
      +  id: 'phewaspvalues',
      +  ...options,
      +  label: {
      +    text: '{{{{namespace[phewas]}}trait_label}}',
      +    filters: [
      +      {
      +        field: '{{namespace[phewas]}}log_pvalue',
      +        operator: '>=',
      +        value: 20,
      +      },
      +    ],
      +  }
      +}

      The following filters are available:

      • =: field value exactly matches the provided value
      • @@ -328,31 +304,31 @@

        Annotations preser

        LocusZoom typically maintains a separation of concerns, in which data layers are responsible for rendering the data provided by an adapter. For the most part, the display layer is not responsible for changing data, and it does not preserve state across renderings

        However, there are some situations where a user might wish to modify how the data is shown: eg “show a label for this one specific point”.

        The basic mechanism for this is called annotations:

        -
        // External code, or tooltips, may set a value
        -data_layer.setElementAnnotation(element_data, key, value);
        -data_layer.getElementAnnotation(item, key);
        +
        // External code, or tooltips, may set a value
        +data_layer.setElementAnnotation(element_data, key, value);
        +data_layer.getElementAnnotation(item, key);

        Essentially, a rendering annotation stores an additional field for a given data point, but that field is stored internally rather than being sent by the data adapter. It can be used in any layout directive that operates on fields: size, shape, color, labels, etc.

        The value can be anything you wish. However, LocusZoom works best when field values are primitives (like strings or integers).

        Typically this is used by tooltips, allowing the user to click a link and modify how a point is shown. See the example below:

        -
        const tooltip = {
        -  // In this example, we take advantage of the fact that each tooltip is bound to the data for a specific point. Internally, D3 stores this information in a field called node.__data__, and we can use this to get the data for a tooltip.
        -  html: `<a href="javascript:void(0);" 
        -          onclick="var item = this.parentNode.__data__, layer = item.getDataLayer(); 
        -          var current = layer.getElementAnnotation(item, 'lz_show_label'); 
        -          layer.setElementAnnotation(item, 'lz_show_label', !current );
        -          layer.parent_plot.applyState();">Toggle label</a>`;`
        -}
        -
        -// The annotation is an additional field, but it doesn't come from the datasource. It can be used in any layout directive that operates on fields: size, shape, color, labels, etc..
        -const scatter_layout = {
        -  ...options,
        -  label: {
        -    ...label_options,
        -    filters: [
        -      { field: 'lz_show_label', operator: '=', value: true }
        -    ],
        -  }
        -};
        +
        const tooltip = {
        +  // In this example, we take advantage of the fact that each tooltip is bound to the data for a specific point. Internally, D3 stores this information in a field called node.__data__, and we can use this to get the data for a tooltip.
        +  html: `<a href="javascript:void(0);" 
        +          onclick="var item = this.parentNode.__data__, layer = item.getDataLayer(); 
        +          var current = layer.getElementAnnotation(item, 'lz_show_label'); 
        +          layer.setElementAnnotation(item, 'lz_show_label', !current );
        +          layer.parent_plot.applyState();">Toggle label</a>`;`
        +}
        +
        +// The annotation is an additional field, but it doesn't come from the datasource. It can be used in any layout directive that operates on fields: size, shape, color, labels, etc..
        +const scatter_layout = {
        +  ...options,
        +  label: {
        +    ...label_options,
        +    filters: [
        +      { field: 'lz_show_label', operator: '=', value: true }
        +    ],
        +  }
        +};

        Matching similar elements across panels

        The primary benefit of LocusZoom is to show results in context, allowing the user to create connections between multiple sources of information.

        When a lot of data is being shown at once, it can be hard to identify exactly which points line up across tracks- each one is quite small! Fortunately, there is a mechanism by which two panels can communicate to find related elements: matching.

        @@ -364,20 +340,20 @@

        Matching similar elements acros
      • The special tag added to these points (lz_is_match) is treated as an extra field, and can be used in any scalable layout directive to control point size, shape, color, filters, etc. This field doesn’t come from the API or data adapter- it is an internal value that is checked on every render.
      • Usage example:

        -
        {
        -  ...options,
        -  // Note that a data layer can send AND receive. This means that it can respond to its own events: "when a match point is clicked, turn that point red"
        -  match: { send: '{{namespace[access]}}target', receive: '{{namespace[access]}}target' },
        -  color: [
        -    {
        -      field: 'lz_is_match', // When a match is detected, it is tagged with a special field name that can be used to trigger custom rendering
        -      scale_function: 'if',
        -      parameters: {
        -        field_value: true,
        -        then: '#ff0000',
        -      },
        -    },
        -}
        +
        {
        +  ...options,
        +  // Note that a data layer can send AND receive. This means that it can respond to its own events: "when a match point is clicked, turn that point red"
        +  match: { send: '{{namespace[access]}}target', receive: '{{namespace[access]}}target' },
        +  color: [
        +    {
        +      field: 'lz_is_match', // When a match is detected, it is tagged with a special field name that can be used to trigger custom rendering
        +      scale_function: 'if',
        +      parameters: {
        +        field_value: true,
        +        then: '#ff0000',
        +      },
        +    },
        +}

        TIP: Matching builds on the primitives described above: it responds to a specific internal event (match_requested), and broadcasts a field to all layers via plot.state.lz_match_value . This means that outside code can also cause the plot to render matching elements, by initiating the rendering update manually: plot.applyState({lz_match_value: your_value_here })

        @@ -386,10 +362,10 @@

        Matching similar elements acros

        Matching rules can be customized

        Matching is not limited to exact value equality. Using a third parameter (“operator”), matching rules can use any of the comparison functions in LocusZoom.MatchFunctions. As described in the description of filtering rules above, match rules can also take into account transforms that modify the field value before it is broadcast, or, how the broadcast value is compared to a specific field. Custom logic (operators) can also be added via MatchFunctions and accessed via name.

        -
        {
        -    ...options,
        -    match: { send: '{{namespace[access]}}target|htmlescape', receive: '{{namespace[access]}}target|htmlescape', operator: '!=' },
        -}
        +
        {
        +    ...options,
        +    match: { send: '{{namespace[access]}}target|htmlescape', receive: '{{namespace[access]}}target|htmlescape', operator: '!=' },
        +}

        NOTE: Remember that the template transforms are also extensible! Each |templatefunction refers to a function in LocusZoom.TransformationFunctions. Add your own via LocusZoom.TransformationFunctions.add('my_function', (value) => othervalue). Custom plugins allow you to create very powerful custom presentations.

        @@ -403,119 +379,119 @@

        Tooltips

        The first interactive feature that most LocusZoom users notice is the tooltip: a simple box that appears with more information when a user interacts with a point.

        In addition to showing data associated with a field, tooltips can be customized with interactive action links that modify plot.state (eg setting the LD reference variant), or trigger annotations (like showing a label).

        A tooltip is defined as an object describing when to display it, as well as the HTML template to render. It responds to the same LocusZoom template syntax supported elsewhere. For example, values can be embedded in the string with curly braces ({{assoc:fieldname}}) and a simple conditional syntax is supported to only render text if a value is defined: {{#if sourcename:field_name}} Conditional text {{#else}} Optional else block {{/if}}.

        -
        {
        -  // Inherits namespaces from the layer that uses this tooltip
        -  namespace: { 'assoc': 'assoc' },
        -  closable: true,
        -  // Show/hide logic is defined by the "element status" for a given piece of data. See `behaviors` for how these statuses get applied.
        -  show: { or: ['highlighted', 'selected'] },
        -  hide: { and: ['unhighlighted', 'unselected'] },
        -  // The tooltip text is an HTML template with access to fields in the data. Be sure to apply HTML-escaping to any user-provided data.
        -  html: `<strong>{{{{namespace[assoc]}}variant|htmlescape}}</strong>`,
        -}
        +
        {
        +  // Inherits namespaces from the layer that uses this tooltip
        +  namespace: { 'assoc': 'assoc' },
        +  closable: true,
        +  // Show/hide logic is defined by the "element status" for a given piece of data. See `behaviors` for how these statuses get applied.
        +  show: { or: ['highlighted', 'selected'] },
        +  hide: { and: ['unhighlighted', 'unselected'] },
        +  // The tooltip text is an HTML template with access to fields in the data. Be sure to apply HTML-escaping to any user-provided data.
        +  html: `<strong>{{{{namespace[assoc]}}variant|htmlescape}}</strong>`,
        +}

        Behaviors

        By default, almost every LocusZoom data element shows a tooltip on mouseover, and keeps that tooltip open when the element is clicked. However, this can be customized.

        See below for an example of a layer where clicking a point acts as a link to a new page.

        -
        {
        -  ...layer_options,
        -  behaviors: {
        -    onmouseover: [
        -      // Most tooltips are configured to appear when the element is highlighted (along with applying any other display tweaks to the page). A guide to statuses is outside the scope of this tutorial, but the default "mouse move actions" are shown for the sake of completeness.
        -      { action: 'set', status: 'highlighted' },
        -    ],
        -    onmouseout: [
        -      { action: 'unset', status: 'highlighted' },
        -    ],
        -    onclick: [
        -        // The href parameter supports LocusZoom's template syntax, allowing data fields to be used in the URL
        -      { action: "link", href: "https://pheweb.org/pheno/{{{{namespace[phewas]}}phewas_code}}" }
        -    ],
        -  },
        -}
        +
        {
        +  ...layer_options,
        +  behaviors: {
        +    onmouseover: [
        +      // Most tooltips are configured to appear when the element is highlighted (along with applying any other display tweaks to the page). A guide to statuses is outside the scope of this tutorial, but the default "mouse move actions" are shown for the sake of completeness.
        +      { action: 'set', status: 'highlighted' },
        +    ],
        +    onmouseout: [
        +      { action: 'unset', status: 'highlighted' },
        +    ],
        +    onclick: [
        +        // The href parameter supports LocusZoom's template syntax, allowing data fields to be used in the URL
        +      { action: "link", href: "https://pheweb.org/pheno/{{{{namespace[phewas]}}phewas_code}}" }
        +    ],
        +  },
        +}

        Toolbar Widgets

        Toggle between render modes with display_options

        The display_options widget renders a dropdown menu with several possible visualization settings. Each time an item is clicked, it will override the default properties.

        To avoid duplication of code, the original display settings are automatically captured and shown as the “Default” option. Other options in the dropdown menu are specified as a list of layout directives that will be merged into the data layer.

        -
        const gene_selector_widget = {
        -  type: 'display_options',
        -  // Below: special config specific to this widget
        -  button_html: 'Filter...',
        -  button_title: 'Choose which genes to show',
        -  // If you are tracking website analytics, this widget can announce when it performs an action. Since this generic widget might be used in several different ways in the same plot, you can give each widget a custom event name to help tell the buttons apart.
        -  // This is totally optional- most sites will be fine just ignoring the event altogether!  
        -  custom_event_name: 'widget_gene_filter_choice',
        -  // Must specify the data layer id (within this panel) that will be controlled by the button
        -  layer_name: 'genes',
        -  default_config_display_name: 'Coding genes & rRNA',
        -  options: [
        -    // Specify how each item in the dropdown menu will work
        -    {
        -      display_name: 'All features',
        -      display: {
        -        filters: null,
        -      },
        -    },
        -  ],
        -}
        +
        const gene_selector_widget = {
        +  type: 'display_options',
        +  // Below: special config specific to this widget
        +  button_html: 'Filter...',
        +  button_title: 'Choose which genes to show',
        +  // If you are tracking website analytics, this widget can announce when it performs an action. Since this generic widget might be used in several different ways in the same plot, you can give each widget a custom event name to help tell the buttons apart.
        +  // This is totally optional- most sites will be fine just ignoring the event altogether!  
        +  custom_event_name: 'widget_gene_filter_choice',
        +  // Must specify the data layer id (within this panel) that will be controlled by the button
        +  layer_name: 'genes',
        +  default_config_display_name: 'Coding genes & rRNA',
        +  options: [
        +    // Specify how each item in the dropdown menu will work
        +    {
        +      display_name: 'All features',
        +      display: {
        +        filters: null,
        +      },
        +    },
        +  ],
        +}

        NOTE: The display options widget operates on a whitelist of layout directives: there are some things it cannot customize. This whitelist is expanded based on user requests. Rewriting (mutating) the entire layout is very powerful, and this gradual approach ensures that each new option is tested carefully before being added to a standard widget.

        Modify data retrieval via set_state

        Some data adapters alter how they fetch information based on variables in plot.state. For example, the LDServer can choose to fetch LD from a particular reference population.

        The set_state widget provides a way to set a particular variable (state_field ) to the provided value. When an option is clicked, it will trigger a re-render, including any updated data.

        -
        const ldlz2_pop_selector_menu = {
        -  // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer
        -  type: 'set_state',
        -  button_html: 'LD Population: ',
        -  show_selected: true,
        -  button_title: 'Select LD Population: ',
        -  state_field: 'ld_pop',
        -  // This list below is hardcoded to work with the UMich LDServer, default 1000G populations
        -  //  It can be customized to work with other LD servers that specify population differently
        -  // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations
        -  options: [
        -    { display_name: 'ALL (default)', value: 'ALL' },
        -    { display_name: 'AFR', value: 'AFR' },
        -    { display_name: 'AMR', value: 'AMR' },
        -    { display_name: 'EAS', value: 'EAS' },
        -    { display_name: 'EUR', value: 'EUR' },
        -    { display_name: 'SAS', value: 'SAS' },
        -  ],
        -};
        +
        const ldlz2_pop_selector_menu = {
        +  // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer
        +  type: 'set_state',
        +  button_html: 'LD Population: ',
        +  show_selected: true,
        +  button_title: 'Select LD Population: ',
        +  state_field: 'ld_pop',
        +  // This list below is hardcoded to work with the UMich LDServer, default 1000G populations
        +  //  It can be customized to work with other LD servers that specify population differently
        +  // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations
        +  options: [
        +    { display_name: 'ALL (default)', value: 'ALL' },
        +    { display_name: 'AFR', value: 'AFR' },
        +    { display_name: 'AMR', value: 'AMR' },
        +    { display_name: 'EAS', value: 'EAS' },
        +    { display_name: 'EUR', value: 'EUR' },
        +    { display_name: 'SAS', value: 'SAS' },
        +  ],
        +};

        Control what is shown with filter_field

        Sometimes, a region plot has a lot of information, and the user wants to restrict what is shown. This can be helpful in coaccessibility tracks, for example, which have a very large number of loops.

        This widget appears as a text box in panel toolbars. Options allow control over a specific filter,

        -
        {
        -  type: 'filter_field',
        -  // Must specify the data layer id relative to the panel in which this widget appears
        -  layer_name: 'coaccessibility',
        -  field: '{{namespace[access]}}score',
        -  field_display_html: 'Score',
        -  operator: '>=',
        -  // Optionally, the value entered into the text box can be coerced into a number.
        -  data_type: 'number',
        -}
        +
        {
        +  type: 'filter_field',
        +  // Must specify the data layer id relative to the panel in which this widget appears
        +  layer_name: 'coaccessibility',
        +  field: '{{namespace[access]}}score',
        +  field_display_html: 'Score',
        +  operator: '>=',
        +  // Optionally, the value entered into the text box can be coerced into a number.
        +  data_type: 'number',
        +}

        Extensions

        A major feature of LocusZoom is the ability to update the plot in response to user events. When a user finds an interesting view, they often wish to share that exact region with colleagues.

        We provide an optional LocusZoom extension to help with this. It can be added via a script tag (dist/ext/lz-dynamic-urls.min.js)

        The plot region will appear in the URL as query parameters. For example, https://statgen.github.io/locuszoom/?chrom=10&start=114550452&end=115067678

        -
        ////// Before creating the plot, check the URL to see if a specific viewing region has requested. If so, make that the default when the plot is first rendered.
        -
        -// The web site owner has control over how their URL looks. Here, the parameter "chr" appears in the URL as "chrom". Any top-level parameter in plot.state can be serialized into the URL. LocusZoom will only try to manage the parameters named here.
        -var stateUrlMapping = {chr: "chrom", start: "start", end: "end"};
        -var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping);
        -if (!Object.keys(initialState).length) {
        -    initialState = {chr: 10, start: 114550452, end: 115067678};
        -}
        -// Draw the plot, providing the desired region to draw
        -const layout = LocusZoom.Layouts.get("plot", "standard_association", {state: initialState});
        -window.plot = LocusZoom.populate("#lz-plot", data_sources, layout);
        -
        -// Set up event listeners: Changes in the plot can be reflected in the URL, and vice versa (eg browser back button can go back to
        -//   a previously viewed region)
        -LzDynamicUrls.plotUpdatesUrl(plot, stateUrlMapping);
        -LzDynamicUrls.plotWatchesUrl(plot, stateUrlMapping);
        +
        ////// Before creating the plot, check the URL to see if a specific viewing region has requested. If so, make that the default when the plot is first rendered.
        +
        +// The web site owner has control over how their URL looks. Here, the parameter "chr" appears in the URL as "chrom". Any top-level parameter in plot.state can be serialized into the URL. LocusZoom will only try to manage the parameters named here.
        +var stateUrlMapping = {chr: "chrom", start: "start", end: "end"};
        +var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping);
        +if (!Object.keys(initialState).length) {
        +    initialState = {chr: 10, start: 114550452, end: 115067678};
        +}
        +// Draw the plot, providing the desired region to draw
        +const layout = LocusZoom.Layouts.get("plot", "standard_association", {state: initialState});
        +window.plot = LocusZoom.populate("#lz-plot", data_sources, layout);
        +
        +// Set up event listeners: Changes in the plot can be reflected in the URL, and vice versa (eg browser back button can go back to
        +//   a previously viewed region)
        +LzDynamicUrls.plotUpdatesUrl(plot, stateUrlMapping);
        +LzDynamicUrls.plotWatchesUrl(plot, stateUrlMapping);

        Note: If your web site is a single page application (like vue-router), then another piece of javascript may already be controlling the page URL. DynamicUrls is mainly intended for web sites where the server returns the HTML for each page.

        diff --git a/docs/guides/rendering_layouts.html b/docs/guides/rendering_layouts.html index 57bce8ae..085a6c25 100644 --- a/docs/guides/rendering_layouts.html +++ b/docs/guides/rendering_layouts.html @@ -106,7 +106,6 @@

        Table of Contents

      • Working with the Registry
      • Nested rule example: scalable parameters
      • @@ -118,7 +117,6 @@

        Table of Contents

      • Layouts also control behavior
      • Where to find more information
      • @@ -161,10 +159,10 @@

        How to read a layout

        responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, - dashboard: LocusZoom.Layouts.get('dashboard', 'standard_plot', { unnamespaced: true }), + toolbar: LocusZoom.Layouts.get('toolbar_widgets', 'standard_plot'), panels: [ - LocusZoom.Layouts.get('panel', 'association', { unnamespaced: true, height: 225 }), - LocusZoom.Layouts.get('panel', 'genes', { unnamespaced: true, height: 225 }) + LocusZoom.Layouts.get('panel', 'association', { height: 225 }), + LocusZoom.Layouts.get('panel', 'genes', { height: 225 }) ] });

    In this view, we have abstracted away all the details of what is plotted, and we can just see the basic pieces: this plot has two panels (association data and genes data) that are displayed separately on the same screen. At the plot level, each panel is 225px high, so the total plot height will be the sum of panels (450 px); if more panels are added, the plot height will increase to match. The actual details of what to render are defined as nested layouts (association and genes panels), and the registry also contains predefined options for each of these smaller pieces- LocusZoom.Layouts.get(...) returns a JSON object.

    @@ -176,102 +174,69 @@

    Will this layout work with my data?<
    LocusZoom.Layouts.add('data_layer', 'association_pvalues', {
         namespace: { 'assoc': 'assoc', 'ld': 'ld' },
         type: 'scatter',
    -    fields: ['{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', '{{namespace[assoc]}}log_pvalue', '{{namespace[assoc]}}log_pvalue|logtoscinotation', '{{namespace[assoc]}}ref_allele', '{{namespace[ld]}}state', '{{namespace[ld]}}isrefvar'],
    -    id_field: '{{namespace[assoc]}}variant',
    -    x_axis: {
    -        field: '{{namespace[assoc]}}position'
    -    }
    -});
    + id_field: 'assoc:variant', + x_axis: { + field: 'assoc:position' + } +});

    Key things to notice are:

      -
    1. The data layer will only see the fields requested by name. Even if a field is present in the API payload, it will not be visible in the data layer unless explicitly referenced in the fields array for that data layer.
    2. -
    3. This layout is generic: the use of namespaces means “given association data from somewhere”. See “using namespaces” below for more details on how to use an abstract layout with a specific dataset (via namespaces).
    4. -
    5. Other sections of the layout (such as x_axis) can reference the fields requested, using the same syntax. But the field must always be requested in the fields array.
    6. +
    7. The data layer will see all of the fields provided by the relevant data sources/adapters. Each field will be prefixed by the “local namespace” for that data (eg if both assoc and ld provide a field called id, both will be present and distinct in the joined data: assoc:id and ld:id)
    8. +
    9. This layout is generic: the use of namespaces means “given association data from somewhere”. See “using namespaces” below.
    10. +
    11. Other sections of the layout (such as x_axis) can reference the fields requested, using the same syntax.
    -

    You will sometimes see fields referenced elsewhere with an additional syntax, like {{namespace[assoc]}}variant|htmlescape. The |htmlescape is a transform that affects value display. The fields array only needs to specify the names of fields; transforms can be applied at any time later.

    -

    No system of notation survives contact with developers

    -

    There are some exceptions to this rule- it is difficult to support every possible combination of data sources from every possible set of API conventions.

    -

    One place where this breaks down is dependent sources- eg, LD information is requested relative to the most significant variant in the association data. Differences in field formatting or nomenclature can sometimes break the ability to find the relevant information that the second data source requires; efforts are made to handle common use cases. These sources gradually become more generic over time based on user feedback and needs.

    -

    The other main exception to the fields mechanism involves API endpoints that return complex nested objects (eg the list of genes in a region, a standard dataset that few people will ever need to customize directly). By notation convention, the default LocusZoom layouts will indicate that special behavior is in effect via a dummy field called all, eg

    -
    { 
    -  fields: ['{{namespace[gene]}}all', '{{namespace[constraint]}}all'] 
    -}
    -
    -

    Explanation: a data layer will not fetch from a source unless the fields array references at least one piece of data from that source. Since the genes source contains special code to bypass fetching explicit fields, the data layer uses a dummy field to trigger the request.

    -
    -

    Most standard data types use the system of exact field names. A more detailed guide is beyond the scope of this tutorial; this behavior is governed by the extractFields method of BaseAdapter. (see: guide to working with data)

    +

    You will sometimes see fields referenced elsewhere with an additional syntax, like assoc:variant|htmlescape. The |htmlescape is a transform that affects value display. Transforms can be applied anywhere that a field value is used.

    +

    Every data layer in the registry will contain an automatically-defined property called _auto_fields, describing the names of all fields expected by this layout object. This is a quick guide for a developer who wants to see a summary of what data is used.

    renameFields: resolving tiny differences in naming

    Sometimes, an existing layout is very close to the format of your data, but there are very small cosmetic differences. For example, your API might send a field variant_name instead of variant.

    In this case, a helper function is provided this will recursively rename every usage of a specified field name in a layout:

    -
    // For abstract layouts
    -layout = LocusZoom.Layouts.renameField(layout, '{{namespace[assoc]}}old_name', '{{namespace[assoc]}}new_name');
    -
    -// For concrete layouts
    -layout = LocusZoom.Layouts.renameField(layout, 'my_assoc:old_name', 'my_assoc:new_name');
    -
    -// To remove a transformation function that is no longer interesting. (must be done in a separate step; otherwise transforms will be preserved after the rename)
    -// The last argument (warn_transforms) suppresses the JS console message that tells you when a rename would affect a field that uses transforms. After all, in this case, that's sort of the point.
    -layout = LocusZoom.Layouts.renameField(layout, 'assoc:pvalue|neg_log10', 'assoc:pvalue', false);
    +
    // For abstract layouts
    +layout = LocusZoom.Layouts.renameField(layout, 'assoc:old_name', 'assoc:new_name');
    +
    +// For concrete layouts
    +layout = LocusZoom.Layouts.renameField(layout, 'my_assoc:old_name', 'my_assoc:new_name');
    +
    +// To remove a transformation function that is no longer interesting. (must be done in a separate step; otherwise transforms will be preserved after the rename)
    +// The last argument (warn_transforms) suppresses the JS console message that tells you when a rename would affect a field that uses transforms. After all, in this case, that's sort of the point.
    +layout = LocusZoom.Layouts.renameField(layout, 'assoc:pvalue|neg_log10', 'assoc:pvalue', false);

    NOTE: Sometimes, the differences in data/field names are more than cosmetic: for example, renaming pvalue|neg_log10 to log_pvalue|neg_log10 would not make sense. This helper method will attempt to warn you if template transformation functions are being used on the field you are changing, so that you can ensure that the resulting layouts uses your data in a way that fits the intended meaning.

    +
    +

    Over time, LocusZoom will become more stringent about what field names it expects to receive. As such, it may make more sense to rename certain fields from the server to match the layout (rather than renaming the layout to match the data). (the _annotateRecords method of a custom adapter can be used for this purpose) Most fields work either way; some, like log_pvalue, are a special case.

    +

    Working with the Registry

    Typically, LocusZoom layouts are loaded via the registry, a set of pre-made reusable layouts. The act of fetching a layout converts it from the abstract definition to one that works with a specific dataset.

    Since the registry just returns JSON-serializable objects, you could create a plot or panel configuration by hand. But this is often tedious, and using the registry will save you from writing many lines of boilerplate code.

    What pieces are available?

    To see the list of pre-defined layouts (as well as any custom ones you have created):

    -
    > LocusZoom.Layouts.list();
    -{plot: Array(4), panel: Array(7), data_layer: Array(9), dashboard: Array(4), dashboard_components: Array(1), tooltip: Array(5) }
    +
    > LocusZoom.Layouts.list();
    +{plot: Array(4), panel: Array(7), data_layer: Array(9), toolbar: Array(4), toolbar_widgets: Array(1), tooltip: Array(5) }

    This will return a top level representation of the available types, showing the available categories. Note that even data layers are composed of smaller building blocks. This lets you use individual parts of a representation (like association tooltips) without committing to the other design choices for a common piece (like the association layer).

    Asking for just the plots shows a list of specific options. Typically, we try to provide example pages that use (most) of the layouts that come with LocusZoom; see the example gallery to preview how these layouts would look.

    -
    > LocusZoom.Layouts.list('plot');
    -(4) ["standard_association", "association_catalog", "standard_phewas", "interval_association"]
    +
    > LocusZoom.Layouts.list('plot');
    +(4) ["standard_association", "association_catalog", "standard_phewas", "interval_association"]

    You may find that the example gallery performs additional customizations on a layout, so this can be a source of ideas. Typically, we try very hard to not make major changes in a layout that is widely in use. Any backwards-incompatible changes will usually be identified in the release notes.

    -

    Abstract vs concrete

    -

    When a layout is first loaded into the registry, it is defined to work in the abstract- given any data. This allows the same layout to be re-used on two different association datasets. The syntax used is {{namespace[assoc]}}variant, where namespaces are replaced by a specific datasource name later on.

    -
    LocusZoom.Layouts.add('data_layer', 'association_pvalues', { // Define a new datalayer
    -    namespace: { 'assoc': 'assoc', 'ld': 'ld' },  // Provides default namespaces, eg look for "assoc" data in a source called "assoc"
    -    fields: ['{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', '{{namespace[assoc]}}log_pvalue', '{{namespace[assoc]}}log_pvalue|logtoscinotation', '{{namespace[assoc]}}ref_allele', '{{namespace[ld]}}state', '{{namespace[ld]}}isrefvar'],
    -});
    -

    It is the act of fetching the layout from the registry that turns it into a concrete one- “find the association data from a particular source”. There are three ways to fetch something, and each behaves in a unique way:

    -
      -
    1. Fetch an existing layout, and tell it where to find the required data. (the third argument, “modifications”, is given an explicit set of namespaces)

      -
      > LocusZoom.Layouts.get('data_layer', 'association_pvalues', { namespace: { assoc: 'my_dataset_1' } } );
      -{
      -    fields: ["my_dataset_1:variant","my_dataset_1:position","my_dataset_1:log_pvalue","my_dataset_1:log_pvalue|logtoscinotation","my_dataset_1:ref_allele","ld:state","ld:isrefvar"]
      -}
    2. -
    3. Fetch an existing layout, and use the “default” data sources. If you follow the examples very closely (eg naming your data source “assoc” and “ld”), this will automatically find the right data.

      -
      > LocusZoom.Layouts.get('data_layer', 'association_pvalues');
      -{
      -    fields: ["assoc:variant", "assoc:position", "assoc:log_pvalue", "assoc:log_pvalue|logtoscinotation", "assoc:ref_allele", "ld:state", "ld:isrefvar"] 
      -}
    4. -
    5. Fetch an existing abstract layout for further modification, and keep it abstract. Note the special unnamespaced: true option, which causes the layout to be returned exactly as it appears in the registry (abstract). This option is used quite a lot in the LZ source code (Layouts.js), because it makes it easy to build a reusable abstract layout (like a panel) out of smaller reusable pieces (like datalayers).

      -
      > LocusZoom.Layouts.get('data_layer', 'association_pvalues', { unnamespaced: true });
      -{
      -    namespace: { assoc: 'assoc', ld: 'ld' },
      -    fields: ['{{namespace[assoc]}}variant', '{{namespace[assoc]}}position', '{{namespace[assoc]}}log_pvalue', '{{namespace[assoc]}}log_pvalue|logtoscinotation', '{{namespace[assoc]}}ref_allele', '{{namespace[ld]}}state', '{{namespace[ld]}}isrefvar'],
      -}
    6. -

    Modifying all (or part) of a layout

    If you are building your own API aimed at use with LocusZoom, then the path of least resistance is to use the same field names as the pre-defined layouts.

    The most straightforward way to modify a layout is to pass just a few overrides. This works will for simple values (like strings), or keys of nested objects:

    Consider an association plot, where the only requested change is to use the right side y-axis (2) instead of the left side y-axis (1, the default). This can be accomplished by adding a key to the third argument of LocusZoom.Layouts.get(...). Note how the other existing options are preserved.

    -
    > LocusZoom.Layouts.get('data_layer', 'association_pvalues', { namespace: { assoc: 'my_dataset_1' }, y_axis: { axis: 2 } } );
    -{ 
    -  y_axis: { axis: 2, field: "my_dataset_1:log_pvalue", floor: 0, upper_buffer: 0.1, min_extent: [0, 10] }
    -}
    +
    > LocusZoom.Layouts.get('data_layer', 'association_pvalues', { namespace: { assoc: 'my_dataset_1' }, y_axis: { axis: 2 } } );
    +{ 
    +  y_axis: { axis: 2, field: "my_dataset_1:log_pvalue", floor: 0, upper_buffer: 0.1, min_extent: [0, 10] }
    +}

    The “modifications” object does not work as well for compound values, like a list, because this behavior is not well defined: changing the 5th element of a list could mean replacement, removal, or minor additions to fields… etc. In practice, this is quite often relevant… because panels and data layers are specified as lists. (order matters)

    -

    For complex scenarios like adding toolbar buttons or overriding the panels/data layers in a plot, you can build your own layout using all or some of the pieces of the layouts registry. One trick that is commonly used in the LocusZoom.js source code is to modify just one part of an existing array field via self-calling functions that immediately return a new, modified object. (this creates the modifications in-place, without leaving any temporary variables around afterwards) Eg, for a toolbar (dashboard) that adds just one extra button to an existing layout:

    -
    {
    -    dashboard: (function () {
    -        var base = LocusZoom.Layouts.get('dashboard', 'standard_panel', { unnamespaced: true });
    -        base.components.push({
    -            type: 'toggle_legend',
    -            position: 'right'
    -        });
    -        return base;
    -    })() // Function calls itself immediately, so "dashboard" is set to the return value
    -}
    +

    For complex scenarios like adding toolbar buttons or overriding the panels/data layers in a plot, you can build your own layout using all or some of the pieces of the layouts registry. One trick that is commonly used in the LocusZoom.js source code is to modify just one part of an existing array field via self-calling functions that immediately return a new, modified object. (this creates the modifications in-place, without leaving any temporary variables around afterwards) Eg, for a toolbar that adds just one extra button to an existing layout:

    +
    {
    +    toolbar: (function () {
    +        var base = LocusZoom.Layouts.get('toolbar', 'standard_panel');
    +        base.widgets.push({
    +            type: 'toggle_legend',
    +            position: 'right'
    +        });
    +        return base;
    +    })() // Function calls itself immediately, so "toolbar" is set to the return value
    +}

    Currently, modifying every level of a deeply nested layout is not an ideal process. Although the above trick (combined with our efforts at backwards compatibility) makes the process possible without copying hundreds of lines of code, we are exploring other, more ergonomic ways to customize layouts in the future.

    TIP: When modifying one option from a list (like changing the label of a toolbar button), it is tempting to address it using simple JavaScript operations, like widgets[0].button_html += 'something'. However, this becomes very hard to read and maintain over time: if someone adds a new button to the start of the list, it will not be obvious that the meaning of button[0] has changed (or what the correct meaning should be). Try to use clear variable names like const display_options = button[0] to convey intent, and override the smallest reusable piece possible. Layouts are defined from the bottom up, not the top down!

    @@ -279,34 +244,34 @@

    Modifying all (or part) of a layoutNested rule example: scalable parameters

    Each key in a layout object corresponds to a set of options supported by the thing that the configuration is intended to control. For example, scatter plots provide simple options that obey exactly one rule (like what to show on the x-axis), but there are also scalable parameters like point_size and point_shape, in which case several rules can be tried in sequence until the first non-null result is found.

    Consider the rules below: in a classic LocusZoom plot, points are colored by LD information, or shown in grey if no LD information is available.

    -
    const data_layer = { 
    -    ...options,
    -    color: [
    -    {
    -        // Name of a function specified in `LocusZoom.ScaleFunctions`
    -        scale_function: 'if',
    -        // The field whose value will be passed to the scale function
    -        field: '{{namespace[ld]}}isrefvar',
    -        // Options that will be passed to the scale function; see documentation for available options
    -        parameters: {
    -            field_value: 1,
    -            then: '#9632b8',
    -        },
    -    },
    -    {
    -        scale_function: 'numerical_bin',
    -        field: '{{namespace[ld]}}state',
    -        parameters: {
    -            breaks: [0, 0.2, 0.4, 0.6, 0.8],
    -            values: ['#357ebd', '#46b8da', '#5cb85c', '#eea236', '#d43f3a'],
    -        },
    -    },
    -    '#B8B8B8',
    -]
    -}
    +
    const data_layer = { 
    +    ...options,
    +    color: [
    +    {
    +        // Name of a function specified in `LocusZoom.ScaleFunctions`
    +        scale_function: 'if',
    +        // The field whose value will be passed to the scale function
    +        field: 'lz_is_ld_refvar',
    +        // Options that will be passed to the scale function; see documentation for available options
    +        parameters: {
    +            field_value: 1,
    +            then: '#9632b8',
    +        },
    +    },
    +    {
    +        scale_function: 'numerical_bin',
    +        field: 'ld:correlation',
    +        parameters: {
    +            breaks: [0, 0.2, 0.4, 0.6, 0.8],
    +            values: ['#357ebd', '#46b8da', '#5cb85c', '#eea236', '#d43f3a'],
    +        },
    +    },
    +    '#B8B8B8',
    +]
    +}

    Common scalable parameters include point size, color, and shape. The full developer documentation marks which parameters are scalable for each data layer rendering type, and describes the options that can be used with each scaling function rule.

    Custom scaling functions may be defined as follows:

    -
    LocusZoom.ScaleFunctions.add('myfunction', (parameters, field_value) => calculated_result_or_null); 
    +
    LocusZoom.ScaleFunctions.add('myfunction', (parameters, field_value) => calculated_result_or_null); 

    Documentation by example

    Composition of smaller pieces is a powerful strategy- but it also means that no single documentation page can explain every feature (because important behavior emerges when two building blocks are combined). As such, examples are a key and explicit part of how LocusZoom usage is documented.

    We encourage you to look at the builtin layouts (via the JS console, or source code) as a guide to “what works”, and the example gallery (with example code) to see what options look like in practice. The full developer documentation enumerates every possible layout configuration option for each plot, panel, data layer rendering type, or rule.

    @@ -318,74 +283,48 @@

    Plotting more than one study

    Using namespaces

    A namespaced layout (usually for panels and below) is one that identifies where to find the relevant data. Due to how separation of concerns works, this requires coordination between the data sources (content) and the layout (presentation).

    Consider the following example, which plots two association studies and a genes track:

    -
    // Other standard sources (genes, LD) omitted for clarity
    -data_sources
    -  .add("assoc_study1", ["AssociationLZ", {url: "/api/association/", params: { source: 1 }}])
    -  .add("assoc_study2", ["AssociationLZ", {url: "/api/association/", params: { source: 2 }}]);
    -
    // This outer call to Layouts.get() will ensure that namespaces are applied, and the returned result is a concrete 
    -//   layout ready for use in drawing a plot with specific data sets. 
    -const plot_layout = LocusZoom.Layouts.get('plot', 'standard_association', { // Override select fields of a pre-made layout 
    -    responsive_resize: true,
    -    panels: [
    -        LocusZoom.Layouts.get('panel', 'association', {
    -            namespace: { assoc: 'assoc_study1' }, // This is the key piece. It says "for this panel, and its child data layers, look for the association data in a datasource called "assoc_study1".
    -            height: 400,
    -            id: 'assoc_study1', // Give each panel a unique ID
    -            title: { text: 'Study 1' },
    -        }),
    -        LocusZoom.Layouts.get('panel', 'association', {
    -            namespace: { assoc: 'assoc_study2' },
    -            height: 400,
    -            id: 'assoc_study2',
    -            title: { text: 'Study 2' },
    -        }),
    -        // Even though genes are part of the original "standard association plot" layout, overriding the panels array means replacing *all* of the panels.
    -        LocusZoom.Layouts.get('panel', 'genes', { unnamespaced: true, height: 400 })  // "unnamespaced" when using as a generic building block
    -    ]
    -});
    -

    Namespaces are not only external- they propagate through to how the layer actually sees its data internally. The field names inside the layer are a composite of sourcename:fieldname. For example, this allows the same data layer to work with two pieces of information (like summary stats and LD) even if both API endpoints provide a common field name like id. Namespacing ensures that duplicate field names do not collide.

    -

    From the example above, the second panel (which fetches association data from assoc_study2) converts a “generic” specification like {{namespace[assoc]}}variant into a “concrete” use of one dataset: assoc_study2:variant.

    -
    > var layer_data = Object.keys(plot.panels['assoc_study2'].data_layers['associationpvalues'].data);
    -Object.keys(layer_data[0]); // See the field names used by the data for a single point
    -["assoc_study2:variant", "assoc_study2:position", "assoc_study2:log_pvalue", "assoc_study2:log_pvalue|logtoscinotation", "assoc_study2:ref_allele", "ld:state", "ld:isrefvar"]
    +
    // Other standard sources (genes, LD) omitted for clarity
    +data_sources
    +  .add("assoc_study1", ["AssociationLZ", {url: "/api/association/", source: 1 }])
    +  .add("assoc_study2", ["AssociationLZ", {url: "/api/association/", source: 2 }])
    +  .add("gene", ["GeneLZ", { url: apiBase + "annotation/genes/", build: 'GRCh37' }]);
    +
    // This outer call to Layouts.get() will ensure that namespaces are applied, and the returned result is a concrete 
    +//   layout ready for use in drawing a plot with specific data sets. 
    +const plot_layout = LocusZoom.Layouts.get('plot', 'standard_association', { // Override select fields of a pre-made layout 
    +    responsive_resize: true,
    +    panels: [
    +        LocusZoom.Layouts.get('panel', 'association', {
    +            namespace: { assoc: 'assoc_study1' }, // This is the key piece. It says "for this panel, and its child data layers, look for the association data in a datasource called "assoc_study1".
    +            height: 400,
    +            id: 'assoc_study1', // Give each panel a unique ID
    +            title: { text: 'Study 1' },
    +        }),
    +        LocusZoom.Layouts.get('panel', 'association', {
    +            namespace: { assoc: 'assoc_study2' },
    +            height: 400,
    +            id: 'assoc_study2',
    +            title: { text: 'Study 2' },
    +        }),
    +        // Even though genes are part of the original "standard association plot" layout, overriding the panels array means replacing *all* of the panels.
    +        LocusZoom.Layouts.get('panel', 'genes', { height: 400 })  // The layout provides a built-in default: "look for gene data in an adapter called gene". In this example, the default matches what sources were defined, so we don't need to override it.
    +    ]
    +});
    +

    Namespaces are the only thing that needs to be changed in order to use a layout with a different source of data. Internally, things are referred to via the “local” name. Eg given { assoc: 'assoc_study2' }, individual fields would be referenced as assoc:some_field, no matter where the data actually came from.

    Adding panels

    The above example demonstrates how to add multiple studies at the time of plot creation. However, sites like the T2D Portal have many datasets, and it can be helpful to let the user interactively choose which other panels to show after first render. New panels can be added dynamically! When doing so, the plot will grow to accommodate the new panel.

    -
    // This creates a new configuration object
    -var extra_panel_layout = LocusZoom.Layouts.get('panel', 'association', {
    -    namespace: { assoc: 'assoc_study3' },
    -    id: 'assoc_study3',
    -    title: { text: 'Study 3' },
    -    y_index: -1 // Special option used by addPanel: inserts this study right before the genes track
    -});
    -
    -// Must add both data sources and panels
    -existing_datasources
    -  .add("assoc_study3", ["AssociationLZ", {url: "/api/association/", params: { source: 3 }}]);
    -
    -const new_panel = existing_plot.addPanel(extra_panel_layout); // Adds the panel and redraws plot
    -

    Common issues

    -

    One of the most common issues in working with namespaced layouts is the difference between abstract and concrete layouts. For example, imagine if the page contained a button to toggle the display of labels on the plot. This button might work by changing the plot layout options, then triggering a re-render.

    -

    Here is how label options would be specified when defining a layout in the registry (abstract), so that labels were present on the very first re-render for any dataset that used this layout. Note that it identifies the key fields using a generic namespace reference:

    -
    const generic_layout_with_special_labels = {
    -    // Other data layer fields omitted for clarity
    -    label: {
    -        text: '{{{{namespace[assoc]}}variant|htmlescape}}',
    -        filters: [ // Only label points if they are above significance threshold
    -            { field: '{{namespace[assoc]}}log_pvalue',  operator: '>', value: 7.301 },
    -        ],
    -        spacing: 6,
    -    }
    -};
    -

    However, once the final layout has been created and used to draw the plot, mutating the layout would require the actual field name (with namespace applied), specific to the panel being changed:

    -
    plot.panels['assoc_study2'].data_layers['associationpvalues'].layout.label = {
    -    text: '{{assoc_study2:variant|htmlescape}}', // Extra outer curly braces around name = Value of field
    -    filters: [ // Only label points if they are above significance threshold
    -        { field: 'assoc_study2:log_pvalue',  operator: '>', value: 7.301 }, // No curly braces: specifies name of field
    -    ],
    -    spacing: 6,
    -};
    -plot.applyState(); // to re-render with labels
    -

    When you are not sure what notation convention to use, check the rest of your layout- the JS console is a very powerful tool, and development is much easier when you can introspect the actual behavior of your code.

    +
    // This creates a new configuration object
    +var extra_panel_layout = LocusZoom.Layouts.get('panel', 'association', {
    +    namespace: { assoc: 'assoc_study3' },
    +    id: 'assoc_study3',
    +    title: { text: 'Study 3' },
    +    y_index: -1 // Special option used by addPanel: inserts this study right before the genes track
    +});
    +
    +// Must add both data sources and panels
    +existing_datasources
    +  .add("assoc_study3", ["AssociationLZ", {url: "/api/association/", source: 3 }]);
    +
    +const new_panel = existing_plot.addPanel(extra_panel_layout); // Adds the panel and redraws plot

    Layouts also control behavior

    In this guide, we have mostly focused on Layouts as a tool for controlling how data is rendered. However, layouts also provide configuration directives to enable powerful behaviors such as zooming, matching, and filtering. See the guide to interactivity for details.

    Where to find more information

    diff --git a/esm/version.js b/esm/version.js index e105cc40..f0ea1679 100644 --- a/esm/version.js +++ b/esm/version.js @@ -1 +1 @@ -export default '0.13.4'; +export default '0.14.0-beta.1'; diff --git a/index.html b/index.html index a08ddc1b..39eb2547 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,7 @@

    Get LocusZoom.js

    CSS
    - https://cdn.jsdelivr.net/npm/locuszoom@0.13.3/dist/locuszoom.css + https://cdn.jsdelivr.net/npm/locuszoom@0.14.0-beta.1/dist/locuszoom.css
    All CSS classes are namespaced to avoid collisions. Use <link crossorigin="anonymous" ...> to ensure that saving images works correctly.
    @@ -96,7 +96,7 @@
    Dependencies
    Javascript
    diff --git a/package-lock.json b/package-lock.json index d8717c82..4d40b0ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.13.4", + "version": "0.14.0-beta.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 67b7c5b3..52661637 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.13.4", + "version": "0.14.0-beta.1", "main": "dist/locuszoom.app.min.js", "module": "esm/index.js", "sideEffects": true, From 53ba107dc31e610609d8e92d019dff8b7d7b61cf Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 11 Nov 2021 17:04:20 -0500 Subject: [PATCH 083/100] Adjust effect direction scaling function for equivalent of a low significance threshold (p~0.05 per indiv snp) Discussion note: In the future we could test pvalue directly to decide significance, instead of beta+/-se --- esm/helpers/scalable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esm/helpers/scalable.js b/esm/helpers/scalable.js index 8acd0aa6..16799316 100644 --- a/esm/helpers/scalable.js +++ b/esm/helpers/scalable.js @@ -236,9 +236,9 @@ function effect_direction(parameters, input) { if (beta_val !== undefined) { if (se_val !== undefined) { - if ((beta_val - 2 * se_val) > 0) { + if ((beta_val - 1.96 * se_val) > 0) { return plus_result; - } else if ((beta_val + 2 * se_val) < 0) { + } else if ((beta_val + 1.96 * se_val) < 0) { return neg_result || null; } } else { From 9fa65e67123196fef7dcb591120c4992ea432c1c Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 16 Nov 2021 17:02:28 -0500 Subject: [PATCH 084/100] Basic ribbon-style "LD" legend --- esm/components/legend.js | 45 ++++++++++++++++++++++++++++++++++++++-- esm/layouts/index.js | 16 +++++++------- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/esm/components/legend.js b/esm/components/legend.js index 1fdff66c..918f709e 100644 --- a/esm/components/legend.js +++ b/esm/components/legend.js @@ -100,8 +100,9 @@ class Legend { let y = padding; let line_height = 0; this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => { - if (Array.isArray(this.parent.data_layers[id].layout.legend)) { - this.parent.data_layers[id].layout.legend.forEach((element) => { + const layer_legend = this.parent.data_layers[id].layout.legend; + if (Array.isArray(layer_legend)) { + layer_legend.forEach((element) => { const selector = this.elements_group.append('g') .attr('transform', `translate(${x}, ${y})`); const label_size = +element.label_size || +this.layout.label_size || 12; @@ -135,6 +136,46 @@ class Legend { label_x = width + padding; line_height = Math.max(line_height, height + padding); + } else if (shape === 'ribbon') { + // Color ribbons describe a series of color stops: small boxes of color across a continuous + // scale. Drawn like: + // [red | orange | yellow | green ] label + // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way. + const width = +element.width || 25; + const height = +element.height || width; + const color_stops = element.color_stops; + const ribbon_group = selector.append('g'); + let axis_offset = 0; + if (element.tick_labels) { + const scale = d3.scaleLinear() + .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode + .range([0, width * color_stops.length - 1]); // 1 px offset to align tick with inner borders + const axis = d3.axisTop(scale) + .tickSize(3) + .tickValues(element.tick_labels) + .tickFormat((v) => v); + ribbon_group.call(axis); + axis_offset += ribbon_group.node().getBoundingClientRect().height; + } + ribbon_group + .attr('transform', `translate(${0}, ${axis_offset})`); + + for (let i = 0; i < color_stops.length; i++) { + const color = color_stops[i]; + ribbon_group + .append('rect') + .attr('class', element.class || '') + .attr('stroke', 'black') + .attr('transform', `translate(${width * i}, 0)`) + .attr('stroke-width', 0.5) + .attr('width', width) + .attr('height', height) + .attr('fill', color) + .call(applyStyles, element.style || {}); + } + + label_x = width * color_stops.length + padding; + label_y += axis_offset; } else if (shape_factory) { // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.) const size = +element.size || 40; diff --git a/esm/layouts/index.js b/esm/layouts/index.js index e4039478..c700b347 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -213,13 +213,15 @@ const association_pvalues_layer = { '#AAAAAA', ], legend: [ - { shape: 'diamond', color: '#9632b8', size: 40, label: 'LD Ref Var', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(219, 61, 17)', size: 40, label: '1.0 > r² ≥ 0.8', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(248, 195, 42)', size: 40, label: '0.8 > r² ≥ 0.6', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(110, 254, 104)', size: 40, label: '0.6 > r² ≥ 0.4', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(38, 188, 225)', size: 40, label: '0.4 > r² ≥ 0.2', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(70, 54, 153)', size: 40, label: '0.2 > r² ≥ 0.0', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: '#AAAAAA', size: 40, label: 'no r² data', class: 'lz-data_layer-scatter' }, + { + shape: 'ribbon', + label: 'LD (r²)', + width: 25, + height: 5, + color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'], + tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0], + label_size: 10, + }, ], label: null, z_index: 2, From b3b5c803c44034bae30b11fc72ce39fb19d039a2 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Tue, 16 Nov 2021 20:14:11 -0500 Subject: [PATCH 085/100] Support both vertical and horizontal ribbon legends; make association panels 20% taller by default --- css/locuszoom.scss | 2 +- esm/components/legend.js | 56 ++++++++++++++++++++++++++++++++-------- esm/layouts/index.js | 9 ++++--- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/css/locuszoom.scss b/css/locuszoom.scss index cd6252ae..7015aea3 100644 --- a/css/locuszoom.scss +++ b/css/locuszoom.scss @@ -92,7 +92,7 @@ svg.#{$namespace}-locuszoom { path.#{$namespace}-data_layer-scatter, path.#{$namespace}-data_layer-category_scatter { stroke: #{$default_black_shadow}; stroke-opacity: #{$default_black_shadow_opacity}; - stroke-width: 1px; + stroke-width: 0.8px; cursor: pointer; } diff --git a/esm/components/legend.js b/esm/components/legend.js index 918f709e..e52c2472 100644 --- a/esm/components/legend.js +++ b/esm/components/legend.js @@ -138,35 +138,63 @@ class Legend { line_height = Math.max(line_height, height + padding); } else if (shape === 'ribbon') { // Color ribbons describe a series of color stops: small boxes of color across a continuous - // scale. Drawn like: + // scale. Drawn horizontally, or vertically, like: // [red | orange | yellow | green ] label // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way. const width = +element.width || 25; const height = +element.height || width; - const color_stops = element.color_stops; - const ribbon_group = selector.append('g'); + const is_horizontal = (element.orientation || 'vertical') === 'horizontal'; + let color_stops = element.color_stops; + + const all_elements = selector.append('g'); + const ribbon_group = all_elements.append('g'); + const axis_group = all_elements.append('g'); let axis_offset = 0; if (element.tick_labels) { + let range; + if (is_horizontal) { + range = [0, width * color_stops.length - 1]; // 1 px offset to align tick with inner borders + } else { + range = [height * color_stops.length - 1, 0]; + } const scale = d3.scaleLinear() .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode - .range([0, width * color_stops.length - 1]); // 1 px offset to align tick with inner borders - const axis = d3.axisTop(scale) + .range(range); + const axis = (is_horizontal ? d3.axisTop : d3.axisRight)(scale) .tickSize(3) .tickValues(element.tick_labels) .tickFormat((v) => v); - ribbon_group.call(axis); - axis_offset += ribbon_group.node().getBoundingClientRect().height; + axis_group.call(axis); + let bcr = axis_group.node().getBoundingClientRect(); + axis_offset = bcr.height; + } + if (is_horizontal) { + // Shift axis down (so that tick marks aren't above the origin) + axis_group + .attr('transform', `translate(0, ${axis_offset})`); + // Ribbon appears below axis + ribbon_group + .attr('transform', `translate(0, ${axis_offset})`); + } else { + // Vertical mode: Shift axis ticks to the right of the ribbon + all_elements.attr('transform', 'translate(5, 0)'); + axis_group + .attr('transform', `translate(${width}, 0)`); } - ribbon_group - .attr('transform', `translate(${0}, ${axis_offset})`); + if (!is_horizontal) { + // Vertical mode: renders top -> bottom but scale is usually specified low..high + color_stops = color_stops.slice(); + color_stops.reverse(); + } for (let i = 0; i < color_stops.length; i++) { const color = color_stops[i]; + const to_next_marking = is_horizontal ? `translate(${width * i}, 0)` : `translate(0, ${height * i})`; ribbon_group .append('rect') .attr('class', element.class || '') .attr('stroke', 'black') - .attr('transform', `translate(${width * i}, 0)`) + .attr('transform', to_next_marking) .attr('stroke-width', 0.5) .attr('width', width) .attr('height', height) @@ -174,7 +202,13 @@ class Legend { .call(applyStyles, element.style || {}); } - label_x = width * color_stops.length + padding; + // Note: In vertical mode, it's usually easier to put the label above the legend as a separate marker + // This is because the legend element label is drawn last (can't use it's size to position the ribbon, which is drawn first) + if (!is_horizontal && element.label) { + throw new Error('Legend labels not supported for vertical ribbons (use a separate legend item as text instead)'); + } + // This only makes sense for horizontal labels. + label_x = (width * color_stops.length + padding); label_y += axis_offset; } else if (shape_factory) { // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.) diff --git a/esm/layouts/index.js b/esm/layouts/index.js index c700b347..146d5310 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -213,11 +213,12 @@ const association_pvalues_layer = { '#AAAAAA', ], legend: [ + { label: 'LD (r²)' }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8' { shape: 'ribbon', - label: 'LD (r²)', - width: 25, - height: 5, + orientation: 'vertical', + width: 10, + height: 15, color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'], tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0], label_size: 10, @@ -742,7 +743,7 @@ const association_panel = { id: 'association', tag: 'association', min_height: 200, - height: 225, + height: 275, margin: { top: 35, right: 50, bottom: 40, left: 50 }, inner_border: 'rgb(210, 210, 210)', toolbar: (function () { From d324733a211443295cee9d3a0f66aa01d3181372 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 17 Nov 2021 18:24:37 -0500 Subject: [PATCH 086/100] 20-40% larger text by default, plus use relative offsets in many places for easier scaling in the future --- css/locuszoom.scss | 17 +++++++++++---- esm/components/data_layer/genes.js | 3 ++- esm/components/legend.js | 8 +++++--- esm/components/panel.js | 6 ++---- esm/ext/lz-credible-sets.js | 2 +- esm/ext/lz-intervals-enrichment.js | 8 ++++---- esm/ext/lz-intervals-track.js | 2 +- esm/layouts/index.js | 33 +++++++++++++++--------------- examples/ext/phewas_forest.html | 2 +- 9 files changed, 45 insertions(+), 36 deletions(-) diff --git a/css/locuszoom.scss b/css/locuszoom.scss index 7015aea3..757c3286 100644 --- a/css/locuszoom.scss +++ b/css/locuszoom.scss @@ -16,7 +16,7 @@ svg.#{$namespace}-locuszoom { border: none; cursor: crosshair; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 10px; + font-size: 14px; rect.#{$namespace}-clickarea { fill: rgb(0,0,0); @@ -40,7 +40,7 @@ svg.#{$namespace}-locuszoom { } .#{$namespace}-panel-title { - font-size: 18px; + font-size: 150%; font-weight: 600; } @@ -57,11 +57,20 @@ svg.#{$namespace}-locuszoom { shape-rendering: crispEdges; } + .#{$namespace}-axis { + // Override the font-size applied by D3 + font-size: 100%; + } + .#{$namespace}-axis .#{$namespace}-label { - font-size: 12px; + font-size: 120%; font-weight: 600; } + .tick { + font-size: 100%; + } + .tick text:focus { outline-style: dotted; outline-width: 1px; @@ -458,7 +467,7 @@ div.#{$namespace}-data_layer-tooltip-arrow_bottom_right { top: 0px; right: 0px; padding: 2px 4px; - font-size: 10px; + font-size: 11px; font-weight: 300; background-color: #D8D8D8; color: #333333; diff --git a/esm/components/data_layer/genes.js b/esm/components/data_layer/genes.js index 88c85da3..be4e44c5 100644 --- a/esm/components/data_layer/genes.js +++ b/esm/components/data_layer/genes.js @@ -11,7 +11,7 @@ const default_layout = { // Optionally specify different fill and stroke properties stroke: 'rgb(54, 54, 150)', color: '#363696', - label_font_size: 12, + label_font_size: 15, label_exon_spacing: 3, exon_height: 10, bounding_box_padding: 3, @@ -266,6 +266,7 @@ class Genes extends BaseDataLayer { const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary') .data([gene], (d) => `${d.gene_name}_boundary`); + // FIXME: Make gene text font sizes scalable height = 1; boundaries.enter() .append('rect') diff --git a/esm/components/legend.js b/esm/components/legend.js index e52c2472..7bef5c16 100644 --- a/esm/components/legend.js +++ b/esm/components/legend.js @@ -18,7 +18,7 @@ const default_layout = { width: 10, height: 10, padding: 5, - label_size: 12, + label_size: 14, hidden: false, }; @@ -105,7 +105,7 @@ class Legend { layer_legend.forEach((element) => { const selector = this.elements_group.append('g') .attr('transform', `translate(${x}, ${y})`); - const label_size = +element.label_size || +this.layout.label_size || 12; + const label_size = +element.label_size || +this.layout.label_size; let label_x = 0; let label_y = (label_size / 2) + (padding / 2); line_height = Math.max(line_height, label_size + padding); @@ -164,7 +164,9 @@ class Legend { .tickSize(3) .tickValues(element.tick_labels) .tickFormat((v) => v); - axis_group.call(axis); + axis_group + .call(axis) + .attr('class', 'lz-axis'); let bcr = axis_group.node().getBoundingClientRect(); axis_offset = bcr.height; } diff --git a/esm/components/panel.js b/esm/components/panel.js index 27500a34..2f36f784 100644 --- a/esm/components/panel.js +++ b/esm/components/panel.js @@ -103,7 +103,8 @@ class Panel { * genomic coordinates, eg 23423456 => 23.42 (Mb) * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated) * @param {string} [layout.axes.y1.label] Label text for the provided axis - * @param {number} [layout.axes.y1.label_offset] + * @param {number} [layout.axes.y1.label_offset] The distance between the axis title and the axis. Use this to prevent + * the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly. * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated) * @param {string} [layout.axes.y2.label] Label text for the provided axis @@ -433,7 +434,6 @@ class Panel { * @returns {BaseDataLayer} */ addDataLayer(layout) { - // Sanity checks if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) { throw new Error('Invalid data layer layout'); @@ -988,7 +988,6 @@ class Panel { * @returns {Panel} */ initialize() { - // Append a container group element to house the main panel group element and the clip path // Position with initial layout parameters const base_id = this.getBaseId(); @@ -1314,7 +1313,6 @@ class Panel { * @returns {Panel} */ renderAxis(axis) { - if (!['x', 'y1', 'y2'].includes(axis)) { throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`); } diff --git a/esm/ext/lz-credible-sets.js b/esm/ext/lz-credible-sets.js index 26ed74e2..686cc1e1 100644 --- a/esm/ext/lz-credible-sets.js +++ b/esm/ext/lz-credible-sets.js @@ -254,7 +254,7 @@ function install (LocusZoom) { title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } }, min_height: 50, height: 50, - margin: { top: 25, right: 50, bottom: 10, left: 50 }, + margin: { top: 25, right: 50, bottom: 10, left: 70 }, inner_border: 'rgb(210, 210, 210)', toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel'), axes: { diff --git a/esm/ext/lz-intervals-enrichment.js b/esm/ext/lz-intervals-enrichment.js index 5558502a..2e176ba0 100644 --- a/esm/ext/lz-intervals-enrichment.js +++ b/esm/ext/lz-intervals-enrichment.js @@ -159,7 +159,7 @@ function install(LocusZoom) { hide: { and: ['unhighlighted', 'unselected'] }, html: `Tissue: {{intervals:tissueId|htmlescape}}
    Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
    - -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
    + -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
    Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}`, }; @@ -257,18 +257,18 @@ function install(LocusZoom) { tag: 'intervals_enrichment', min_height: 250, height: 250, - margin: { top: 35, right: 50, bottom: 40, left: 50 }, + margin: { top: 35, right: 50, bottom: 40, left: 70 }, inner_border: 'rgb(210, 210, 210)', axes: { x: { label: 'Chromosome {{chr}} (Mb)', - label_offset: 32, + label_offset: 34, tick_format: 'region', extent: 'state', }, y1: { label: 'enrichment (n-fold)', - label_offset: 28, + label_offset: 40, }, }, interaction: { diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index c0806641..da8b3600 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -721,7 +721,7 @@ function install (LocusZoom) { tag: 'intervals', min_height: 50, height: 50, - margin: { top: 25, right: 150, bottom: 5, left: 50 }, + margin: { top: 25, right: 150, bottom: 5, left: 70 }, toolbar: (function () { const l = LocusZoom.Layouts.get('toolbar', 'standard_panel'); l.widgets.push({ diff --git a/esm/layouts/index.js b/esm/layouts/index.js index 146d5310..d1b5e633 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -213,7 +213,7 @@ const association_pvalues_layer = { '#AAAAAA', ], legend: [ - { label: 'LD (r²)' }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8' + { label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8' { shape: 'ribbon', orientation: 'vertical', @@ -221,7 +221,6 @@ const association_pvalues_layer = { height: 15, color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'], tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0], - label_size: 10, }, ], label: null, @@ -743,8 +742,8 @@ const association_panel = { id: 'association', tag: 'association', min_height: 200, - height: 275, - margin: { top: 35, right: 50, bottom: 40, left: 50 }, + height: 300, + margin: { top: 35, right: 55, bottom: 40, left: 70 }, inner_border: 'rgb(210, 210, 210)', toolbar: (function () { const base = deepCopy(standard_panel_toolbar); @@ -757,22 +756,22 @@ const association_panel = { axes: { x: { label: 'Chromosome {{chr}} (Mb)', - label_offset: 32, + label_offset: 38, tick_format: 'region', extent: 'state', }, y1: { label: '-log10 p-value', - label_offset: 28, + label_offset: 50, }, y2: { label: 'Recombination Rate (cM/Mb)', - label_offset: 40, + label_offset: 46, }, }, legend: { orientation: 'vertical', - origin: { x: 55, y: 40 }, + origin: { x: 75, y: 40 }, hidden: true, }, interaction: { @@ -799,19 +798,19 @@ const coaccessibility_panel = { tag: 'coaccessibility', min_height: 150, height: 180, - margin: { top: 35, right: 50, bottom: 40, left: 50 }, + margin: { top: 35, right: 55, bottom: 40, left: 70 }, inner_border: 'rgb(210, 210, 210)', toolbar: deepCopy(standard_panel_toolbar), axes: { x: { label: 'Chromosome {{chr}} (Mb)', - label_offset: 32, + label_offset: 38, tick_format: 'region', extent: 'state', }, y1: { label: 'Score', - label_offset: 28, + label_offset: 40, render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter. }, }, @@ -871,7 +870,7 @@ const association_catalog_panel = function () { { field: 'ld:correlation', operator: '>', value: 0.4 }, ], style: { - 'font-size': '10px', + 'font-size': '12px', 'font-weight': 'bold', 'fill': '#333333', }, @@ -897,7 +896,7 @@ const genes_panel = { tag: 'genes', min_height: 150, height: 225, - margin: { top: 20, right: 50, bottom: 20, left: 50 }, + margin: { top: 20, right: 55, bottom: 20, left: 70 }, axes: {}, interaction: { drag_background_to_pan: true, @@ -930,7 +929,7 @@ const phewas_panel = { tag: 'phewas', min_height: 300, height: 300, - margin: { top: 20, right: 50, bottom: 120, left: 50 }, + margin: { top: 20, right: 55, bottom: 120, left: 70 }, inner_border: 'rgb(210, 210, 210)', axes: { x: { @@ -946,7 +945,7 @@ const phewas_panel = { }, y1: { label: '-log10 p-value', - label_offset: 28, + label_offset: 50, }, }, data_layers: [ @@ -965,7 +964,7 @@ const annotation_catalog_panel = { tag: 'gwascatalog', min_height: 50, height: 50, - margin: { top: 25, right: 50, bottom: 10, left: 50 }, + margin: { top: 25, right: 55, bottom: 10, left: 70 }, inner_border: 'rgb(210, 210, 210)', toolbar: deepCopy(standard_panel_toolbar), axes: { @@ -1039,7 +1038,7 @@ const standard_phewas_plot = { axes: { x: { label: 'Chromosome {{chr}} (Mb)', - label_offset: 32, + label_offset: 38, tick_format: 'region', extent: 'state', }, diff --git a/examples/ext/phewas_forest.html b/examples/ext/phewas_forest.html index 4751150c..c8d4e79d 100644 --- a/examples/ext/phewas_forest.html +++ b/examples/ext/phewas_forest.html @@ -78,7 +78,7 @@
    < return home
    axes: { x: { label: "Beta", - label_offset: 33 + label_offset: 34 }, y2: { ticks: { // Dynamically generated ticks, but specify custom options/styles to be used in every tick From f833512de2d0ece6667ba9f18399f18f4759cc41 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 18 Nov 2021 13:29:50 -0500 Subject: [PATCH 087/100] Update stale README text and screenshots --- README.md | 28 +++++++++--------- ...locuszoom_standard_association_example.png | Bin 0 -> 78695 bytes test/unit/test_layouts.js | 4 +-- 3 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 examples/locuszoom_standard_association_example.png diff --git a/README.md b/README.md index 93a63d5f..41600cba 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ LocusZoom is a Javascript/d3 embeddable plugin for interactively visualizing statistical genetic data from customizable sources. -**This is a low level library aimed at developers who want to customize their own data sharing/visualization. If you are a genetics researcher who just wants to make a fast visualization of your research results, try our user-friendly plot-your-own data services built on LocusZoom.js: [my.locuszoom.org](https://my.locuszoom.org/) and [LocalZoom](https://statgen.github.io/localzoom/)**. +**This is a low level library aimed at developers who want to customize their own data sharing/visualization tools. If you are a genetics researcher who just wants to make a fast visualization of your research results, try our user-friendly plot-your-own data services built on LocusZoom.js: [my.locuszoom.org](https://my.locuszoom.org/) and [LocalZoom](https://statgen.github.io/localzoom/)**. ![Build Status](https://github.com/statgen/locuszoom/workflows/Unit%20tests/badge.svg?branch=develop) @@ -11,13 +11,13 @@ See [https://statgen.github.io/locuszoom/docs/](https://statgen.github.io/locusz To see functional examples of plots generated with LocusZoom.js see [statgen.github.io/locuszoom](http://statgen.github.io/locuszoom/) and [statgen.github.io/locuszoom/#examples](http://statgen.github.io/locuszoom/#examples). -![LocusZoom.js Standard Association Plot](http://statgen.github.io/locuszoom/wiki_images/locuszoom_standard_association_plot_0.5.2.png) +![LocusZoom.js Standard Association Plot](examples/locuszoom_standard_association_example.png) ## Making a LocusZoom Plot ### 1. Include Necessary JavaScript and CSS -The page you build that embeds the LocusZoom plugin must include the following resources, found in the `dist` directory: +The page you build that embeds the LocusZoom plugin must include the following resources, found in the `dist` directory (or via CDN): * `d3.js` [D3.js](https://d3js.org/) v5.16.0 is used to draw graphics in LocusZoom plots. It may be loaded [via a CDN](https://cdn.jsdelivr.net/npm/d3@^5.16.0). It must be present before LocusZoom is loaded. @@ -98,15 +98,15 @@ A basic example may then look like this: ```html - - + +
    @@ -133,11 +133,11 @@ A basic example may then look like this: #### Use a Predefined Layout -The core LocusZoom library comes equipped with several predefined layouts, organized by type ("plot", "panel", "data_layer", and "toolbar"). 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)`). +The core LocusZoom library comes equipped with several predefined layouts, organized by type ("plot", "panel", "data_layer", and "toolbar"). You can see what layouts are predefined by reading the [documentation](https://statgen.github.io/locuszoom/docs/api/module-LocusZoom_Layouts.html) or introspecting in the browser by entering `LocusZoom.Layouts.list()` (or to list one specific type, like "data_layer": `LocusZoom.Layouts.list(type)`). Get any predefined layout by type and name using `LocusZoom.Layouts.get(type, name)`. -If your data matches the field names and formats of the [UMich PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#overview-of-api-endpoints), these layouts will provide a quick way to get started. If your data obeys different format rules, customization may be necessary. +If your data matches the field names and formats of the [UMich PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#overview-of-api-endpoints), these layouts will provide a quick way to get started. If your data obeys different format rules, customization may be necessary. (for example, some LocusZoom features assume the presence of a field called `log_pvalue`) See the [guide to working with layouts](https://statgen.github.io/locuszoom/docs/guides/rendering_layouts.html) for further details. @@ -167,8 +167,8 @@ const 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 -const changes = { label_font_size: 20 }; -LocusZoom.Layouts.get("data_layer", "genes", changes); +const overrides = { label_font_size: 20 }; +LocusZoom.Layouts.get("data_layer", "genes", overrides); ``` #### Predefining State by Building a State Object @@ -229,9 +229,9 @@ LocusZoom runs code quality checks via [ESLint](http://eslint.org/), the rules f ## Help and Support -Full documentation can be found here: [docs/](https://statgen.github.io/locuszoom/docs/) +Full API documentation and prose guides are available at: [https://statgen.github.io/locuszoom/docs/](https://statgen.github.io/locuszoom/docs/) -A LocusZoom discussion forum is available here: [groups.google.com/forum/#!forum/locuszoom](https://groups.google.com/forum/#!forum/locuszoom). +A LocusZoom discussion forum is available here: [https://groups.google.com/forum/#!forum/locuszoom](https://groups.google.com/forum/#!forum/locuszoom). For the most effective help, please specify that your question is about "LocusZoom.js". If you have questions or feedback please file an issue on the [LocusZoom.js GitHub repository](https://github.com/statgen/locuszoom/issues) or post at the discussion forum referenced above. diff --git a/examples/locuszoom_standard_association_example.png b/examples/locuszoom_standard_association_example.png new file mode 100644 index 0000000000000000000000000000000000000000..7b4b29fb950d4062811c5080e69d715920524787 GIT binary patch literal 78695 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 literal 0 HcmV?d00001 diff --git a/test/unit/test_layouts.js b/test/unit/test_layouts.js index 11f22901..55c2c397 100644 --- a/test/unit/test_layouts.js +++ b/test/unit/test_layouts.js @@ -357,8 +357,8 @@ describe('Layout helpers', function () { ['set many values to a constant', '$..id', 'all', ['all', 'all', 'all', 'all', 'all', 'all']], ['add items to an array', '$.some_list', (old_value) => old_value.concat(['field1', 'field2']), [['field0', 'field1', 'field2']]], // Two subtly different cases for nested objects (direct query, and as part of filtered list) - ['mutate an object inside an object', '$..panels[?(@.tag === "association")].margin', (old_config) => (old_config.new_field = 10) && old_config, [{bottom: 40, left: 50, right: 50, top: 35, new_field: 10}]], - ['mutate an object inside a list', '$..panels[?(@.tag === "association")]', (old_config) => (old_config.margin.new_field = 10) && old_config, [Object.assign(base_panel, {margin: {bottom: 40, left: 50, right: 50, top: 35, new_field: 10}})]], + ['mutate an object inside an object', '$..panels[?(@.tag === "association")].margin', (old_config) => (old_config.new_field = 10) && old_config, [{bottom: 40, left: 70, right: 55, top: 35, new_field: 10}]], + ['mutate an object inside a list', '$..panels[?(@.tag === "association")]', (old_config) => (old_config.margin.new_field = 10) && old_config, [Object.assign(base_panel, {margin: {bottom: 40, left: 70, right: 55, top: 35, new_field: 10}})]], ]; for (let [label, selector, mutator, expected] of scenarios) { From 242ef8c4726687da3ff6671199bdab589fe4099d Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 18 Nov 2021 14:28:06 -0500 Subject: [PATCH 088/100] Document new LegendItem options for ribbon --- esm/components/data_layer/base.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 77534866..1c7536fd 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -72,7 +72,8 @@ import SCALABLE from '../../registry/scalable'; * @typedef {object} LegendItem * @property [shape] This is optional (e.g. a legend element could just be a textual label). * Supported values are the standard d3 3.x symbol types (i.e. "circle", "cross", "diamond", "square", - * "triangle-down", and "triangle-up"), as well as "rect" for an arbitrary square/rectangle or line for a path. + * "triangle-down", and "triangle-up"), as well as "rect" for an arbitrary square/rectangle or "line" for a path. + * A special "ribbon" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD) * @property {string} color The point color (hexadecimal, rgb, etc) * @property {string} label The human-readable label of the legend item * @property {string} [class] The name of a CSS class used to style the point in the legend @@ -81,9 +82,12 @@ import SCALABLE from '../../registry/scalable'; * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element * if the value of the shape parameter is "line". * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if - * the value of the shape parameter is "rect". + * the value of the shape parameter is "rect" or "ribbon". * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if - * the value of the shape parameter is "rect". + * the value of the shape parameter is "rect" or "ribbon". + * @property {'vertical'|'horizontal'} [orientation='vertical'] For shape "ribbon", specifies whether to draw the ribbon vertically or horizontally. + * @property {Array} [tick_labels] For shape "ribbon", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops. + * @property {String[]} [color_stops] For shape "ribbon", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels. * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of * the legend element. */ From 71720dea275062ea4f2a6384c843c2cbf2f3f1a6 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 18 Nov 2021 15:05:57 -0500 Subject: [PATCH 089/100] Commit several release-support files required to use prerelease features in ESM mode --- dist/locuszoom.css | 2 +- dist/locuszoom.css.map | 2 +- esm/version.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/locuszoom.css b/dist/locuszoom.css index 2145bb75..abb56d7e 100644 --- a/dist/locuszoom.css +++ b/dist/locuszoom.css @@ -1 +1 @@ -svg.lz-locuszoom{background-color:#fff;border:none;cursor:crosshair;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:10px}svg.lz-locuszoom rect.lz-clickarea{fill:#000;fill-opacity:0;cursor:pointer}svg.lz-locuszoom .lz-mouse_guide rect{fill:#d2d2d2;fill-opacity:.85}svg.lz-locuszoom .lz-mouse_guide rect.lz-mouse_guide-vertical{width:1px;height:100%}svg.lz-locuszoom .lz-mouse_guide rect.lz-mouse_guide-horizontal{width:100%;height:1px}svg.lz-locuszoom .lz-panel-title{font-size:18px;font-weight:600}svg.lz-locuszoom .lz-panel-background{fill:#fff;fill-opacity:.01}svg.lz-locuszoom .lz-axis path,svg.lz-locuszoom .lz-axis line{fill:none;stroke:rgb(24, 24, 24);stroke-opacity:1;shape-rendering:crispEdges}svg.lz-locuszoom .lz-axis .lz-label{font-size:12px;font-weight:600}svg.lz-locuszoom .tick text:focus{outline-style:dotted;outline-width:1px;outline-color:#424242;outline-offset:2px}svg.lz-locuszoom .lz-legend-background{fill:#f5f5f5;fill-opacity:1;stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom path.lz-data_layer-arcs-selected{stroke:rgb(24, 24, 24) !important;stroke-opacity:1 !important;stroke-width:2px !important}svg.lz-locuszoom path.lz-data_layer-arcs-highlighted{stroke:rgb(24, 24, 24) !important;stroke-opacity:0.4 !important;stroke-width:2px !important}svg.lz-locuszoom path.lz-data_layer-scatter,svg.lz-locuszoom path.lz-data_layer-category_scatter{stroke:rgb(24, 24, 24);stroke-opacity:0.4;stroke-width:1px;cursor:pointer}svg.lz-locuszoom path.lz-data_layer-scatter-highlighted,svg.lz-locuszoom path.lz-data_layer-category_scatter-highlighted{stroke:rgb(24, 24, 24);stroke-opacity:0.4;stroke-width:4px}svg.lz-locuszoom path.lz-data_layer-scatter-selected,svg.lz-locuszoom path.lz-data_layer-category_scatter-selected{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:4px}svg.lz-locuszoom path.lz-data_layer-scatter-faded,svg.lz-locuszoom path.lz-data_layer-category_scatter-faded{fill-opacity:.1;stroke-opacity:.1}svg.lz-locuszoom path.lz-data_layer-scatter-hidden,svg.lz-locuszoom path.lz-data_layer-category_scatter-hidden{display:none}svg.lz-locuszoom text.lz-data_layer-scatter-label,svg.lz-locuszoom text.lz-data_layer-category_scatter-label{fill:rgb(24, 24, 24);fill-opacity:1;alignment-baseline:middle}svg.lz-locuszoom line.lz-data_layer-scatter-label,svg.lz-locuszoom line.lz-data_layer-category_scatter-label{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom rect.lz-data_layer-annotation_track{cursor:pointer}svg.lz-locuszoom path.lz-data_layer-annotation_track-highlighted,svg.lz-locuszoom path.lz-data_layer-annotation_track-selected{stroke-opacity:1}svg.lz-locuszoom path.lz-data_layer-line{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px;cursor:pointer}svg.lz-locuszoom path.lz-data_layer-line-faded{stroke-opacity:.2}svg.lz-locuszoom path.lz-data_layer-line-hidden{display:none}svg.lz-locuszoom path.lz-data_layer-line-hitarea{fill:none;stroke:none;cursor:pointer}svg.lz-locuszoom g.lz-data_layer-genes{cursor:pointer}svg.lz-locuszoom g.lz-data_layer-genes-faded{opacity:.1}svg.lz-locuszoom g.lz-data_layer-genes-hidden{display:none}svg.lz-locuszoom text.lz-data_layer-genes.lz-label{font-style:italic}svg.lz-locuszoom rect.lz-data_layer-genes.lz-data_layer-genes-statusnode{fill:#363696;fill-opacity:0;stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-genes.lz-data_layer-genes-statusnode-highlighted{fill:#363696;fill-opacity:.1;stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-genes.lz-data_layer-genes-statusnode-selected{fill:#363696;fill-opacity:.15;stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom rect.lz-data_layer-genes.lz-boundary{stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom rect.lz-data_layer-genes.lz-exon{stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom g.lz-data_layer-intervals-faded{opacity:.1}svg.lz-locuszoom g.lz-data_layer-intervals-hidden{display:none}svg.lz-locuszoom rect.lz-data_layer-intervals.lz-data_layer-intervals-statusnode{fill:#363696;fill-opacity:0;stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-intervals.lz-data_layer-intervals-statusnode-highlighted{fill:#363696;fill-opacity:.2;stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-intervals.lz-data_layer-intervals-statusnode-selected{fill:#363696;fill-opacity:.35;stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom g.lz-data_layer-intervals{stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-intervals-highlighted,svg.lz-locuszoom rect.lz-data_layer-intervals-selected{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom path.lz-data_layer-forest{stroke:rgb(24, 24, 24);stroke-opacity:0.4;stroke-width:1px;cursor:pointer}svg.lz-locuszoom path.lz-data_layer-forest-highlighted{stroke:rgb(24, 24, 24);stroke-opacity:0.4;stroke-width:4px}svg.lz-locuszoom path.lz-data_layer-forest-selected{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:4px}svg.lz-locuszoom path.lz-data_layer-forest-faded{fill-opacity:.1;stroke-opacity:.1}svg.lz-locuszoom path.lz-data_layer-forest-hidden{display:none}svg.lz-locuszoom rect.lz-data_layer-forest.lz-data_layer-ci{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}div.lz-data_layer-tooltip{font-family:"Helvetica Neue",Helvetica,Aria,sans-serif;font-size:12px;position:absolute;padding:6px;background:#ebebeb;border:1px solid rgb(24, 24, 24);border-radius:4px;box-shadow:2px 2px 2px rgba(24, 24, 24, 0.4)}div.lz-data_layer-tooltip h1,div.lz-data_layer-tooltip h2,div.lz-data_layer-tooltip h3,div.lz-data_layer-tooltip h4,div.lz-data_layer-tooltip h5,div.lz-data_layer-tooltip h6{margin-top:.1em;margin-bottom:.3em}div.lz-data_layer-tooltip h1{font-size:200%}div.lz-data_layer-tooltip h2{font-size:175%}div.lz-data_layer-tooltip h3{font-size:150%}div.lz-data_layer-tooltip h4{font-size:135%}div.lz-data_layer-tooltip h5{font-size:125%}div.lz-data_layer-tooltip h6{font-size:110%}div.lz-data_layer-tooltip table{display:table;border-collapse:separate;border-spacing:1px;margin-bottom:.2em}div.lz-data_layer-tooltip th:first-child,div.lz-data_layer-tooltip td:first-child{padding-left:0px}div.lz-data_layer-tooltip th,div.lz-data_layer-tooltip td{padding:6px;text-align:left;border-bottom:1px solid #e1e1e1}div.lz-data_layer-tooltip button{text-decoration:none;height:auto;line-height:inherit;padding:.2em .5em .2em .5em;background-color:#d8d8d8;color:rgb(24, 24, 24);border:1px solid rgb(24, 24, 24);border-radius:4px;pointer-events:auto;outline:none}div.lz-data_layer-tooltip button:hover{cursor:pointer;background-color:rgb(24, 24, 24);color:rgb(232, 232, 232)}div.lz-data_layer-tooltip .lz-tooltip-close-button{color:rgba(24, 24, 24, 0.4);border:1px solid rgba(24, 24, 24, 0.4);background-color:rgba(235,235,235,.4);float:right;position:relative;top:-6px;right:-6px;margin:0px 0px 6px 6px}div.lz-data_layer-tooltip-arrow_up{width:0;height:0;pointer-events:none;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_down{width:0;height:0;pointer-events:none;border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_right{width:0;height:0;pointer-events:none;border-top:7px solid transparent;border-bottom:7px solid transparent;border-left:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_left{width:0;height:0;pointer-events:none;border-top:7px solid transparent;border-bottom:7px solid transparent;border-right:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_top_left{width:0;height:0;pointer-events:none;border-top:7px solid rgb(24, 24, 24);border-left:7px solid rgb(24, 24, 24);border-bottom:7px solid transparent;border-right:7px solid transparent}div.lz-data_layer-tooltip-arrow_top_right{width:0;height:0;pointer-events:none;border-top:7px solid rgb(24, 24, 24);border-left:7px solid transparent;border-bottom:7px solid transparent;border-right:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_bottom_left{width:0;height:0;pointer-events:none;border-top:7px solid transparent;border-left:7px solid rgb(24, 24, 24);border-bottom:7px solid rgb(24, 24, 24);border-right:7px solid transparent}div.lz-data_layer-tooltip-arrow_bottom_right{width:0;height:0;pointer-events:none;border-top:7px solid transparent;border-left:7px solid transparent;border-bottom:7px solid rgb(24, 24, 24);border-right:7px solid rgb(24, 24, 24)}.lz-container{display:inline-block;overflow:hidden}.lz-container-responsive{width:100%;display:inline-block;overflow:hidden}.lz-curtain{position:absolute;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;font-weight:600;background:rgba(216,216,216,.8)}.lz-curtain .lz-curtain-content{position:absolute;display:block;width:100%;top:50%;transform:translateY(-50%);margin:0 auto;padding:0px 20px;overflow-y:auto}.lz-curtain .lz-curtain-dismiss{position:absolute;top:0px;right:0px;padding:2px 4px;font-size:10px;font-weight:300;background-color:#d8d8d8;color:#333;border:1px solid #333;border-radius:0px 0px 0px 4px;pointer-events:auto;cursor:pointer}.lz-curtain .lz-curtain-dismiss:hover{background-color:#333;color:#d8d8d8}.lz-loader{position:absolute;font-family:"Helvetica Neue",Helvetica,Aria,sans-serif;font-size:12px;padding:6px;background:#f0ebe4;border:1px solid rgb(24, 24, 24);border-radius:4px;box-shadow:2px 2px 2px rgba(24, 24, 24, 0.4)}.lz-loader .lz-loader-content{position:relative;display:block;width:100%}.lz-loader .lz-loader-progress-container{position:relative;display:block;width:100%;height:2px;padding-top:6px}.lz-loader .lz-loader-progress{position:absolute;left:0%;width:0%;height:2px;background-color:rgba(24, 24, 24, 0.4)}.lz-loader .lz-loader-progress-animated{animation-name:lz-loader-animate;animation-duration:1.5s;animation-iteration-count:infinite;animation-timing-function:ease-in-out}@keyframes lz-loader-animate{0%{width:0%;left:0%}50%{width:100%;left:0%}100%{width:0%;left:100%}}.lz-plot-toolbar{padding-left:0px;padding-right:0px;padding-top:4px;padding-bottom:3px}.lz-plot-toolbar .lz-toolbar-button{text-decoration:none;padding:3px 7px 3px 7px;height:auto;line-height:inherit;border-radius:4px;pointer-events:auto;outline:none;white-space:nowrap;cursor:pointer;box-sizing:border-box;align-items:flex-start;text-align:center}.lz-plot-toolbar .lz-toolbar-button-group-start{border-radius:4px 0px 0px 4px !important}.lz-plot-toolbar .lz-toolbar-button-group-middle{border-radius:0px !important;border-left-width:0px !important}.lz-plot-toolbar .lz-toolbar-button-group-end{border-radius:0px 4px 4px 0px !important;border-left-width:0px !important}.lz-plot-toolbar>.lz-toolbar-left{float:left;margin-right:1.5em}.lz-plot-toolbar>.lz-toolbar-right{float:right;margin-left:1.5em}.lz-plot-toolbar>.lz-toolbar-group-start{margin-right:0em !important}.lz-plot-toolbar>.lz-toolbar-group-middle{margin-left:0em !important;margin-right:0em !important}.lz-plot-toolbar>.lz-toolbar-group-end{margin-left:0em !important}.lz-panel-toolbar{padding-left:2px;padding-right:2px;padding-top:2px;padding-bottom:0px}.lz-panel-toolbar .lz-toolbar-button{text-decoration:none;padding:2px 6px 2px 6px;height:auto;line-height:inherit;border-radius:3px;pointer-events:auto;outline:none;white-space:nowrap;cursor:pointer;box-sizing:border-box;align-items:flex-start;text-align:center}.lz-panel-toolbar .lz-toolbar-button-group-start{border-radius:3px 0px 0px 3px !important}.lz-panel-toolbar .lz-toolbar-button-group-middle{border-radius:0px !important;border-left-width:0px !important}.lz-panel-toolbar .lz-toolbar-button-group-end{border-radius:0px 3px 3px 0px !important;border-left-width:0px !important}.lz-panel-toolbar input{line-height:normal}.lz-panel-toolbar>.lz-toolbar-left{float:left;margin-right:0px}.lz-panel-toolbar>.lz-toolbar-right{float:right;margin-left:0px}.lz-toolbar{font-family:"Helvetica Neue",Helvetica,Aria,sans-serif;font-size:80%}.lz-toolbar .lz-toolbar-title{margin-bottom:.3em}.lz-toolbar .lz-toolbar-title h3{font-size:150%;font-weight:bold;margin:0em}.lz-toolbar .lz-toolbar-title small{font-size:80%;font-weight:normal}div.lz-toolbar-menu{font-family:"Helvetica Neue",Helvetica,Aria,sans-serif;font-size:12px;position:absolute;padding:6px;border-radius:4px;box-shadow:2px 2px 2px rgba(24, 24, 24, 0.4);box-sizing:border-box;overflow:hidden}div.lz-toolbar-menu .lz-toolbar-menu-content{display:block;width:100%;height:100%;overflow-y:auto}div.lz-toolbar-menu .lz-toolbar-menu-content h1,div.lz-toolbar-menu .lz-toolbar-menu-content h2,div.lz-toolbar-menu .lz-toolbar-menu-content h3,div.lz-toolbar-menu .lz-toolbar-menu-content h4,div.lz-toolbar-menu .lz-toolbar-menu-content h5,div.lz-toolbar-menu .lz-toolbar-menu-content h6{margin-top:.1em;margin-bottom:.3em}div.lz-toolbar-menu .lz-toolbar-menu-content h1{font-size:200%}div.lz-toolbar-menu .lz-toolbar-menu-content h2{font-size:175%}div.lz-toolbar-menu .lz-toolbar-menu-content h3{font-size:150%}div.lz-toolbar-menu .lz-toolbar-menu-content h4{font-size:135%}div.lz-toolbar-menu .lz-toolbar-menu-content h5{font-size:125%}div.lz-toolbar-menu .lz-toolbar-menu-content h6{font-size:110%}div.lz-toolbar-menu .lz-toolbar-menu-content .lz-toolbar-button{text-decoration:none;padding:2px 6px 2px 6px;height:auto;line-height:inherit;border-radius:3px;pointer-events:auto;outline:none;white-space:nowrap;cursor:pointer;box-sizing:border-box;align-items:flex-start;text-align:center;font-weight:300;letter-spacing:0;text-transform:none;margin:0}div.lz-toolbar-menu .lz-toolbar-menu-content .lz-toolbar-button-group-start{border-radius:3px 0px 0px 3px !important}div.lz-toolbar-menu .lz-toolbar-menu-content .lz-toolbar-button-group-middle{border-radius:0px !important;border-left-width:0px !important}div.lz-toolbar-menu .lz-toolbar-menu-content .lz-toolbar-button-group-end{border-radius:0px 3px 3px 0px !important;border-left-width:0px !important}div.lz-toolbar-menu table{display:table;border-collapse:collapse;border-spacing:0px;margin-bottom:.4em;width:100%}div.lz-toolbar-menu th:first-child,div.lz-toolbar-menu td:first-child{vertical-align:top}div.lz-toolbar-menu tr:nth-child(odd){background-color:rgba(0,0,0,.08)}div.lz-toolbar-menu th,div.lz-toolbar-menu td{padding:4px;text-align:left;vertical-align:middle;border:none;line-height:1}.lz-toolbar-button-gray{background-color:rgba(216,216,216,.6);border:1px solid rgba(51,51,51,.6);color:rgba(51,51,51,.6)}.lz-toolbar-button-gray:not(.lz-toolbar-button-gray-disabled):not(.lz-toolbar-button-gray-highlighted):hover{background-color:#d8d8d8;border:1px solid #333;color:#333}.lz-toolbar-button-gray-highlighted{border:1px solid #333;background-color:#333;color:#d8d8d8 !important}.lz-toolbar-button-gray-disabled{background-color:rgba(216,216,216,.6);border:1px solid rgba(153,153,153,.6);color:rgba(153,153,153,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-red{background-color:rgba(230,195,195,.6);border:1px solid rgba(89,49,49,.6);color:rgba(89,49,49,.6)}.lz-toolbar-button-red:not(.lz-toolbar-button-red-disabled):not(.lz-toolbar-button-red-highlighted):hover{background-color:#e6c3c3;border:1px solid #593131;color:#593131}.lz-toolbar-button-red-highlighted{border:1px solid #593131;background-color:#593131;color:#e6c3c3 !important}.lz-toolbar-button-red-disabled{background-color:rgba(230,195,195,.6);border:1px solid rgba(179,134,134,.6);color:rgba(179,134,134,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-orange{background-color:rgba(230,213,195,.6);border:1px solid rgba(89,69,49,.6);color:rgba(89,69,49,.6)}.lz-toolbar-button-orange:not(.lz-toolbar-button-orange-disabled):not(.lz-toolbar-button-orange-highlighted):hover{background-color:#e6d5c3;border:1px solid #594531;color:#594531}.lz-toolbar-button-orange-highlighted{border:1px solid #594531;background-color:#594531;color:#e6d5c3 !important}.lz-toolbar-button-orange-disabled{background-color:rgba(230,213,195,.6);border:1px solid rgba(179,156,134,.6);color:rgba(179,156,134,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-yellow{background-color:rgba(230,230,195,.6);border:1px solid rgba(89,89,49,.6);color:rgba(89,89,49,.6)}.lz-toolbar-button-yellow:not(.lz-toolbar-button-yellow-disabled):not(.lz-toolbar-button-yellow-highlighted):hover{background-color:#e6e6c3;border:1px solid #595931;color:#595931}.lz-toolbar-button-yellow-highlighted{border:1px solid #595931;background-color:#595931;color:#e6e6c3 !important}.lz-toolbar-button-yellow-disabled{background-color:rgba(230,230,195,.6);border:1px solid rgba(179,179,134,.6);color:rgba(179,179,134,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-green{background-color:rgba(204,230,195,.6);border:1px solid rgba(59,89,49,.6);color:rgba(59,89,49,.6)}.lz-toolbar-button-green:not(.lz-toolbar-button-green-disabled):not(.lz-toolbar-button-green-highlighted):hover{background-color:#cce6c3;border:1px solid #3b5931;color:#3b5931}.lz-toolbar-button-green-highlighted{border:1px solid #3b5931;background-color:#3b5931;color:#cce6c3 !important}.lz-toolbar-button-green-disabled{background-color:rgba(204,230,195,.6);border:1px solid rgba(145,179,134,.6);color:rgba(145,179,134,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-blue{background-color:rgba(195,210,230,.6);border:1px solid rgba(49,66,89,.6);color:rgba(49,66,89,.6)}.lz-toolbar-button-blue:not(.lz-toolbar-button-blue-disabled):not(.lz-toolbar-button-blue-highlighted):hover{background-color:#c3d2e6;border:1px solid #314259;color:#314259}.lz-toolbar-button-blue-highlighted{border:1px solid #314259;background-color:#314259;color:#c3d2e6 !important}.lz-toolbar-button-blue-disabled{background-color:rgba(195,210,230,.6);border:1px solid rgba(134,153,179,.6);color:rgba(134,153,179,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-purple{background-color:rgba(221,195,230,.6);border:1px solid rgba(80,49,89,.6);color:rgba(80,49,89,.6)}.lz-toolbar-button-purple:not(.lz-toolbar-button-purple-disabled):not(.lz-toolbar-button-purple-highlighted):hover{background-color:#ddc3e6;border:1px solid #503159;color:#503159}.lz-toolbar-button-purple-highlighted{border:1px solid #503159;background-color:#503159;color:#ddc3e6 !important}.lz-toolbar-button-purple-disabled{background-color:rgba(221,195,230,.6);border:1px solid rgba(167,134,179,.6);color:rgba(167,134,179,.6);pointer-events:none;cursor:wait}.lz-toolbar-menu-gray{background-color:#e8e8e8;border:1px solid #333}.lz-toolbar-menu-red{background:#f0e4e4;border:1px solid #593131}.lz-toolbar-menu-orange{background:#f0eae4;border:1px solid #594531}.lz-toolbar-menu-yellow{background:#f0f0e4;border:1px solid #595931}.lz-toolbar-menu-green{background:#e7f0e4;border:1px solid #3b5931}.lz-toolbar-menu-blue{background:#e4e9f0;border:1px solid #314259}.lz-toolbar-menu-purple{background:#ede4f0;border:1px solid #503159}div.lz-panel-boundary{position:absolute;height:3px;cursor:row-resize;display:block;padding-top:10px;padding-bottom:10px}div.lz-panel-boundary span{display:block;border-radius:1px;background:rgba(216,216,216,0);border:1px solid rgba(216,216,216,0);height:3px}div.lz-panel-boundary:hover span{background:#d8d8d8;border:1px solid rgb(24, 24, 24)}div.lz-panel-boundary:active span{background:#a6a6a6;border:1px solid rgb(24, 24, 24)}div.lz-panel-corner-boundary{position:absolute;height:16px;width:16px;cursor:nwse-resize;display:block;padding:10px}div.lz-panel-corner-boundary span.lz-panel-corner-boundary-outer{display:block;width:0px;height:0px;border-bottom:16px solid transparent;border-left:16px solid transparent;position:absolute;top:10px;left:10px}div.lz-panel-corner-boundary span.lz-panel-corner-boundary-inner{display:block;width:0px;height:0px;border-bottom:13px solid transparent;border-left:13px solid transparent;position:absolute;top:12px;left:12px}div.lz-panel-corner-boundary:hover span.lz-panel-corner-boundary-outer{border-bottom:16px solid rgb(24, 24, 24)}div.lz-panel-corner-boundary:hover span.lz-panel-corner-boundary-inner{border-bottom:13px solid #d8d8d8}div.lz-panel-corner-boundary:active span.lz-panel-corner-boundary-inner{border-bottom:13px solid #a6a6a6}/*# sourceMappingURL=locuszoom.css.map */ +svg.lz-locuszoom{background-color:#fff;border:none;cursor:crosshair;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px}svg.lz-locuszoom rect.lz-clickarea{fill:#000;fill-opacity:0;cursor:pointer}svg.lz-locuszoom .lz-mouse_guide rect{fill:#d2d2d2;fill-opacity:.85}svg.lz-locuszoom .lz-mouse_guide rect.lz-mouse_guide-vertical{width:1px;height:100%}svg.lz-locuszoom .lz-mouse_guide rect.lz-mouse_guide-horizontal{width:100%;height:1px}svg.lz-locuszoom .lz-panel-title{font-size:150%;font-weight:600}svg.lz-locuszoom .lz-panel-background{fill:#fff;fill-opacity:.01}svg.lz-locuszoom .lz-axis path,svg.lz-locuszoom .lz-axis line{fill:none;stroke:rgb(24, 24, 24);stroke-opacity:1;shape-rendering:crispEdges}svg.lz-locuszoom .lz-axis{font-size:100%}svg.lz-locuszoom .lz-axis .lz-label{font-size:120%;font-weight:600}svg.lz-locuszoom .tick{font-size:100%}svg.lz-locuszoom .tick text:focus{outline-style:dotted;outline-width:1px;outline-color:#424242;outline-offset:2px}svg.lz-locuszoom .lz-legend-background{fill:#f5f5f5;fill-opacity:1;stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom path.lz-data_layer-arcs-selected{stroke:rgb(24, 24, 24) !important;stroke-opacity:1 !important;stroke-width:2px !important}svg.lz-locuszoom path.lz-data_layer-arcs-highlighted{stroke:rgb(24, 24, 24) !important;stroke-opacity:0.4 !important;stroke-width:2px !important}svg.lz-locuszoom path.lz-data_layer-scatter,svg.lz-locuszoom path.lz-data_layer-category_scatter{stroke:rgb(24, 24, 24);stroke-opacity:0.4;stroke-width:.8px;cursor:pointer}svg.lz-locuszoom path.lz-data_layer-scatter-highlighted,svg.lz-locuszoom path.lz-data_layer-category_scatter-highlighted{stroke:rgb(24, 24, 24);stroke-opacity:0.4;stroke-width:4px}svg.lz-locuszoom path.lz-data_layer-scatter-selected,svg.lz-locuszoom path.lz-data_layer-category_scatter-selected{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:4px}svg.lz-locuszoom path.lz-data_layer-scatter-faded,svg.lz-locuszoom path.lz-data_layer-category_scatter-faded{fill-opacity:.1;stroke-opacity:.1}svg.lz-locuszoom path.lz-data_layer-scatter-hidden,svg.lz-locuszoom path.lz-data_layer-category_scatter-hidden{display:none}svg.lz-locuszoom text.lz-data_layer-scatter-label,svg.lz-locuszoom text.lz-data_layer-category_scatter-label{fill:rgb(24, 24, 24);fill-opacity:1;alignment-baseline:middle}svg.lz-locuszoom line.lz-data_layer-scatter-label,svg.lz-locuszoom line.lz-data_layer-category_scatter-label{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom rect.lz-data_layer-annotation_track{cursor:pointer}svg.lz-locuszoom path.lz-data_layer-annotation_track-highlighted,svg.lz-locuszoom path.lz-data_layer-annotation_track-selected{stroke-opacity:1}svg.lz-locuszoom path.lz-data_layer-line{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px;cursor:pointer}svg.lz-locuszoom path.lz-data_layer-line-faded{stroke-opacity:.2}svg.lz-locuszoom path.lz-data_layer-line-hidden{display:none}svg.lz-locuszoom path.lz-data_layer-line-hitarea{fill:none;stroke:none;cursor:pointer}svg.lz-locuszoom g.lz-data_layer-genes{cursor:pointer}svg.lz-locuszoom g.lz-data_layer-genes-faded{opacity:.1}svg.lz-locuszoom g.lz-data_layer-genes-hidden{display:none}svg.lz-locuszoom text.lz-data_layer-genes.lz-label{font-style:italic}svg.lz-locuszoom rect.lz-data_layer-genes.lz-data_layer-genes-statusnode{fill:#363696;fill-opacity:0;stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-genes.lz-data_layer-genes-statusnode-highlighted{fill:#363696;fill-opacity:.1;stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-genes.lz-data_layer-genes-statusnode-selected{fill:#363696;fill-opacity:.15;stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom rect.lz-data_layer-genes.lz-boundary{stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom rect.lz-data_layer-genes.lz-exon{stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom g.lz-data_layer-intervals-faded{opacity:.1}svg.lz-locuszoom g.lz-data_layer-intervals-hidden{display:none}svg.lz-locuszoom rect.lz-data_layer-intervals.lz-data_layer-intervals-statusnode{fill:#363696;fill-opacity:0;stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-intervals.lz-data_layer-intervals-statusnode-highlighted{fill:#363696;fill-opacity:.2;stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-intervals.lz-data_layer-intervals-statusnode-selected{fill:#363696;fill-opacity:.35;stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom g.lz-data_layer-intervals{stroke-width:0px}svg.lz-locuszoom rect.lz-data_layer-intervals-highlighted,svg.lz-locuszoom rect.lz-data_layer-intervals-selected{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}svg.lz-locuszoom path.lz-data_layer-forest{stroke:rgb(24, 24, 24);stroke-opacity:0.4;stroke-width:1px;cursor:pointer}svg.lz-locuszoom path.lz-data_layer-forest-highlighted{stroke:rgb(24, 24, 24);stroke-opacity:0.4;stroke-width:4px}svg.lz-locuszoom path.lz-data_layer-forest-selected{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:4px}svg.lz-locuszoom path.lz-data_layer-forest-faded{fill-opacity:.1;stroke-opacity:.1}svg.lz-locuszoom path.lz-data_layer-forest-hidden{display:none}svg.lz-locuszoom rect.lz-data_layer-forest.lz-data_layer-ci{stroke:rgb(24, 24, 24);stroke-opacity:1;stroke-width:1px}div.lz-data_layer-tooltip{font-family:"Helvetica Neue",Helvetica,Aria,sans-serif;font-size:12px;position:absolute;padding:6px;background:#ebebeb;border:1px solid rgb(24, 24, 24);border-radius:4px;box-shadow:2px 2px 2px rgba(24, 24, 24, 0.4)}div.lz-data_layer-tooltip h1,div.lz-data_layer-tooltip h2,div.lz-data_layer-tooltip h3,div.lz-data_layer-tooltip h4,div.lz-data_layer-tooltip h5,div.lz-data_layer-tooltip h6{margin-top:.1em;margin-bottom:.3em}div.lz-data_layer-tooltip h1{font-size:200%}div.lz-data_layer-tooltip h2{font-size:175%}div.lz-data_layer-tooltip h3{font-size:150%}div.lz-data_layer-tooltip h4{font-size:135%}div.lz-data_layer-tooltip h5{font-size:125%}div.lz-data_layer-tooltip h6{font-size:110%}div.lz-data_layer-tooltip table{display:table;border-collapse:separate;border-spacing:1px;margin-bottom:.2em}div.lz-data_layer-tooltip th:first-child,div.lz-data_layer-tooltip td:first-child{padding-left:0px}div.lz-data_layer-tooltip th,div.lz-data_layer-tooltip td{padding:6px;text-align:left;border-bottom:1px solid #e1e1e1}div.lz-data_layer-tooltip button{text-decoration:none;height:auto;line-height:inherit;padding:.2em .5em .2em .5em;background-color:#d8d8d8;color:rgb(24, 24, 24);border:1px solid rgb(24, 24, 24);border-radius:4px;pointer-events:auto;outline:none}div.lz-data_layer-tooltip button:hover{cursor:pointer;background-color:rgb(24, 24, 24);color:rgb(232, 232, 232)}div.lz-data_layer-tooltip .lz-tooltip-close-button{color:rgba(24, 24, 24, 0.4);border:1px solid rgba(24, 24, 24, 0.4);background-color:rgba(235,235,235,.4);float:right;position:relative;top:-6px;right:-6px;margin:0px 0px 6px 6px}div.lz-data_layer-tooltip-arrow_up{width:0;height:0;pointer-events:none;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_down{width:0;height:0;pointer-events:none;border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_right{width:0;height:0;pointer-events:none;border-top:7px solid transparent;border-bottom:7px solid transparent;border-left:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_left{width:0;height:0;pointer-events:none;border-top:7px solid transparent;border-bottom:7px solid transparent;border-right:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_top_left{width:0;height:0;pointer-events:none;border-top:7px solid rgb(24, 24, 24);border-left:7px solid rgb(24, 24, 24);border-bottom:7px solid transparent;border-right:7px solid transparent}div.lz-data_layer-tooltip-arrow_top_right{width:0;height:0;pointer-events:none;border-top:7px solid rgb(24, 24, 24);border-left:7px solid transparent;border-bottom:7px solid transparent;border-right:7px solid rgb(24, 24, 24)}div.lz-data_layer-tooltip-arrow_bottom_left{width:0;height:0;pointer-events:none;border-top:7px solid transparent;border-left:7px solid rgb(24, 24, 24);border-bottom:7px solid rgb(24, 24, 24);border-right:7px solid transparent}div.lz-data_layer-tooltip-arrow_bottom_right{width:0;height:0;pointer-events:none;border-top:7px solid transparent;border-left:7px solid transparent;border-bottom:7px solid rgb(24, 24, 24);border-right:7px solid rgb(24, 24, 24)}.lz-container{display:inline-block;overflow:hidden}.lz-container-responsive{width:100%;display:inline-block;overflow:hidden}.lz-curtain{position:absolute;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;font-weight:600;background:rgba(216,216,216,.8)}.lz-curtain .lz-curtain-content{position:absolute;display:block;width:100%;top:50%;transform:translateY(-50%);margin:0 auto;padding:0px 20px;overflow-y:auto}.lz-curtain .lz-curtain-dismiss{position:absolute;top:0px;right:0px;padding:2px 4px;font-size:11px;font-weight:300;background-color:#d8d8d8;color:#333;border:1px solid #333;border-radius:0px 0px 0px 4px;pointer-events:auto;cursor:pointer}.lz-curtain .lz-curtain-dismiss:hover{background-color:#333;color:#d8d8d8}.lz-loader{position:absolute;font-family:"Helvetica Neue",Helvetica,Aria,sans-serif;font-size:12px;padding:6px;background:#f0ebe4;border:1px solid rgb(24, 24, 24);border-radius:4px;box-shadow:2px 2px 2px rgba(24, 24, 24, 0.4)}.lz-loader .lz-loader-content{position:relative;display:block;width:100%}.lz-loader .lz-loader-progress-container{position:relative;display:block;width:100%;height:2px;padding-top:6px}.lz-loader .lz-loader-progress{position:absolute;left:0%;width:0%;height:2px;background-color:rgba(24, 24, 24, 0.4)}.lz-loader .lz-loader-progress-animated{animation-name:lz-loader-animate;animation-duration:1.5s;animation-iteration-count:infinite;animation-timing-function:ease-in-out}@keyframes lz-loader-animate{0%{width:0%;left:0%}50%{width:100%;left:0%}100%{width:0%;left:100%}}.lz-plot-toolbar{padding-left:0px;padding-right:0px;padding-top:4px;padding-bottom:3px}.lz-plot-toolbar .lz-toolbar-button{text-decoration:none;padding:3px 7px 3px 7px;height:auto;line-height:inherit;border-radius:4px;pointer-events:auto;outline:none;white-space:nowrap;cursor:pointer;box-sizing:border-box;align-items:flex-start;text-align:center}.lz-plot-toolbar .lz-toolbar-button-group-start{border-radius:4px 0px 0px 4px !important}.lz-plot-toolbar .lz-toolbar-button-group-middle{border-radius:0px !important;border-left-width:0px !important}.lz-plot-toolbar .lz-toolbar-button-group-end{border-radius:0px 4px 4px 0px !important;border-left-width:0px !important}.lz-plot-toolbar>.lz-toolbar-left{float:left;margin-right:1.5em}.lz-plot-toolbar>.lz-toolbar-right{float:right;margin-left:1.5em}.lz-plot-toolbar>.lz-toolbar-group-start{margin-right:0em !important}.lz-plot-toolbar>.lz-toolbar-group-middle{margin-left:0em !important;margin-right:0em !important}.lz-plot-toolbar>.lz-toolbar-group-end{margin-left:0em !important}.lz-panel-toolbar{padding-left:2px;padding-right:2px;padding-top:2px;padding-bottom:0px}.lz-panel-toolbar .lz-toolbar-button{text-decoration:none;padding:2px 6px 2px 6px;height:auto;line-height:inherit;border-radius:3px;pointer-events:auto;outline:none;white-space:nowrap;cursor:pointer;box-sizing:border-box;align-items:flex-start;text-align:center}.lz-panel-toolbar .lz-toolbar-button-group-start{border-radius:3px 0px 0px 3px !important}.lz-panel-toolbar .lz-toolbar-button-group-middle{border-radius:0px !important;border-left-width:0px !important}.lz-panel-toolbar .lz-toolbar-button-group-end{border-radius:0px 3px 3px 0px !important;border-left-width:0px !important}.lz-panel-toolbar input{line-height:normal}.lz-panel-toolbar>.lz-toolbar-left{float:left;margin-right:0px}.lz-panel-toolbar>.lz-toolbar-right{float:right;margin-left:0px}.lz-toolbar{font-family:"Helvetica Neue",Helvetica,Aria,sans-serif;font-size:80%}.lz-toolbar .lz-toolbar-title{margin-bottom:.3em}.lz-toolbar .lz-toolbar-title h3{font-size:150%;font-weight:bold;margin:0em}.lz-toolbar .lz-toolbar-title small{font-size:80%;font-weight:normal}div.lz-toolbar-menu{font-family:"Helvetica Neue",Helvetica,Aria,sans-serif;font-size:12px;position:absolute;padding:6px;border-radius:4px;box-shadow:2px 2px 2px rgba(24, 24, 24, 0.4);box-sizing:border-box;overflow:hidden}div.lz-toolbar-menu .lz-toolbar-menu-content{display:block;width:100%;height:100%;overflow-y:auto}div.lz-toolbar-menu .lz-toolbar-menu-content h1,div.lz-toolbar-menu .lz-toolbar-menu-content h2,div.lz-toolbar-menu .lz-toolbar-menu-content h3,div.lz-toolbar-menu .lz-toolbar-menu-content h4,div.lz-toolbar-menu .lz-toolbar-menu-content h5,div.lz-toolbar-menu .lz-toolbar-menu-content h6{margin-top:.1em;margin-bottom:.3em}div.lz-toolbar-menu .lz-toolbar-menu-content h1{font-size:200%}div.lz-toolbar-menu .lz-toolbar-menu-content h2{font-size:175%}div.lz-toolbar-menu .lz-toolbar-menu-content h3{font-size:150%}div.lz-toolbar-menu .lz-toolbar-menu-content h4{font-size:135%}div.lz-toolbar-menu .lz-toolbar-menu-content h5{font-size:125%}div.lz-toolbar-menu .lz-toolbar-menu-content h6{font-size:110%}div.lz-toolbar-menu .lz-toolbar-menu-content .lz-toolbar-button{text-decoration:none;padding:2px 6px 2px 6px;height:auto;line-height:inherit;border-radius:3px;pointer-events:auto;outline:none;white-space:nowrap;cursor:pointer;box-sizing:border-box;align-items:flex-start;text-align:center;font-weight:300;letter-spacing:0;text-transform:none;margin:0}div.lz-toolbar-menu .lz-toolbar-menu-content .lz-toolbar-button-group-start{border-radius:3px 0px 0px 3px !important}div.lz-toolbar-menu .lz-toolbar-menu-content .lz-toolbar-button-group-middle{border-radius:0px !important;border-left-width:0px !important}div.lz-toolbar-menu .lz-toolbar-menu-content .lz-toolbar-button-group-end{border-radius:0px 3px 3px 0px !important;border-left-width:0px !important}div.lz-toolbar-menu table{display:table;border-collapse:collapse;border-spacing:0px;margin-bottom:.4em;width:100%}div.lz-toolbar-menu th:first-child,div.lz-toolbar-menu td:first-child{vertical-align:top}div.lz-toolbar-menu tr:nth-child(odd){background-color:rgba(0,0,0,.08)}div.lz-toolbar-menu th,div.lz-toolbar-menu td{padding:4px;text-align:left;vertical-align:middle;border:none;line-height:1}.lz-toolbar-button-gray{background-color:rgba(216,216,216,.6);border:1px solid rgba(51,51,51,.6);color:rgba(51,51,51,.6)}.lz-toolbar-button-gray:not(.lz-toolbar-button-gray-disabled):not(.lz-toolbar-button-gray-highlighted):hover{background-color:#d8d8d8;border:1px solid #333;color:#333}.lz-toolbar-button-gray-highlighted{border:1px solid #333;background-color:#333;color:#d8d8d8 !important}.lz-toolbar-button-gray-disabled{background-color:rgba(216,216,216,.6);border:1px solid rgba(153,153,153,.6);color:rgba(153,153,153,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-red{background-color:rgba(230,195,195,.6);border:1px solid rgba(89,49,49,.6);color:rgba(89,49,49,.6)}.lz-toolbar-button-red:not(.lz-toolbar-button-red-disabled):not(.lz-toolbar-button-red-highlighted):hover{background-color:#e6c3c3;border:1px solid #593131;color:#593131}.lz-toolbar-button-red-highlighted{border:1px solid #593131;background-color:#593131;color:#e6c3c3 !important}.lz-toolbar-button-red-disabled{background-color:rgba(230,195,195,.6);border:1px solid rgba(179,134,134,.6);color:rgba(179,134,134,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-orange{background-color:rgba(230,213,195,.6);border:1px solid rgba(89,69,49,.6);color:rgba(89,69,49,.6)}.lz-toolbar-button-orange:not(.lz-toolbar-button-orange-disabled):not(.lz-toolbar-button-orange-highlighted):hover{background-color:#e6d5c3;border:1px solid #594531;color:#594531}.lz-toolbar-button-orange-highlighted{border:1px solid #594531;background-color:#594531;color:#e6d5c3 !important}.lz-toolbar-button-orange-disabled{background-color:rgba(230,213,195,.6);border:1px solid rgba(179,156,134,.6);color:rgba(179,156,134,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-yellow{background-color:rgba(230,230,195,.6);border:1px solid rgba(89,89,49,.6);color:rgba(89,89,49,.6)}.lz-toolbar-button-yellow:not(.lz-toolbar-button-yellow-disabled):not(.lz-toolbar-button-yellow-highlighted):hover{background-color:#e6e6c3;border:1px solid #595931;color:#595931}.lz-toolbar-button-yellow-highlighted{border:1px solid #595931;background-color:#595931;color:#e6e6c3 !important}.lz-toolbar-button-yellow-disabled{background-color:rgba(230,230,195,.6);border:1px solid rgba(179,179,134,.6);color:rgba(179,179,134,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-green{background-color:rgba(204,230,195,.6);border:1px solid rgba(59,89,49,.6);color:rgba(59,89,49,.6)}.lz-toolbar-button-green:not(.lz-toolbar-button-green-disabled):not(.lz-toolbar-button-green-highlighted):hover{background-color:#cce6c3;border:1px solid #3b5931;color:#3b5931}.lz-toolbar-button-green-highlighted{border:1px solid #3b5931;background-color:#3b5931;color:#cce6c3 !important}.lz-toolbar-button-green-disabled{background-color:rgba(204,230,195,.6);border:1px solid rgba(145,179,134,.6);color:rgba(145,179,134,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-blue{background-color:rgba(195,210,230,.6);border:1px solid rgba(49,66,89,.6);color:rgba(49,66,89,.6)}.lz-toolbar-button-blue:not(.lz-toolbar-button-blue-disabled):not(.lz-toolbar-button-blue-highlighted):hover{background-color:#c3d2e6;border:1px solid #314259;color:#314259}.lz-toolbar-button-blue-highlighted{border:1px solid #314259;background-color:#314259;color:#c3d2e6 !important}.lz-toolbar-button-blue-disabled{background-color:rgba(195,210,230,.6);border:1px solid rgba(134,153,179,.6);color:rgba(134,153,179,.6);pointer-events:none;cursor:wait}.lz-toolbar-button-purple{background-color:rgba(221,195,230,.6);border:1px solid rgba(80,49,89,.6);color:rgba(80,49,89,.6)}.lz-toolbar-button-purple:not(.lz-toolbar-button-purple-disabled):not(.lz-toolbar-button-purple-highlighted):hover{background-color:#ddc3e6;border:1px solid #503159;color:#503159}.lz-toolbar-button-purple-highlighted{border:1px solid #503159;background-color:#503159;color:#ddc3e6 !important}.lz-toolbar-button-purple-disabled{background-color:rgba(221,195,230,.6);border:1px solid rgba(167,134,179,.6);color:rgba(167,134,179,.6);pointer-events:none;cursor:wait}.lz-toolbar-menu-gray{background-color:#e8e8e8;border:1px solid #333}.lz-toolbar-menu-red{background:#f0e4e4;border:1px solid #593131}.lz-toolbar-menu-orange{background:#f0eae4;border:1px solid #594531}.lz-toolbar-menu-yellow{background:#f0f0e4;border:1px solid #595931}.lz-toolbar-menu-green{background:#e7f0e4;border:1px solid #3b5931}.lz-toolbar-menu-blue{background:#e4e9f0;border:1px solid #314259}.lz-toolbar-menu-purple{background:#ede4f0;border:1px solid #503159}div.lz-panel-boundary{position:absolute;height:3px;cursor:row-resize;display:block;padding-top:10px;padding-bottom:10px}div.lz-panel-boundary span{display:block;border-radius:1px;background:rgba(216,216,216,0);border:1px solid rgba(216,216,216,0);height:3px}div.lz-panel-boundary:hover span{background:#d8d8d8;border:1px solid rgb(24, 24, 24)}div.lz-panel-boundary:active span{background:#a6a6a6;border:1px solid rgb(24, 24, 24)}div.lz-panel-corner-boundary{position:absolute;height:16px;width:16px;cursor:nwse-resize;display:block;padding:10px}div.lz-panel-corner-boundary span.lz-panel-corner-boundary-outer{display:block;width:0px;height:0px;border-bottom:16px solid transparent;border-left:16px solid transparent;position:absolute;top:10px;left:10px}div.lz-panel-corner-boundary span.lz-panel-corner-boundary-inner{display:block;width:0px;height:0px;border-bottom:13px solid transparent;border-left:13px solid transparent;position:absolute;top:12px;left:12px}div.lz-panel-corner-boundary:hover span.lz-panel-corner-boundary-outer{border-bottom:16px solid rgb(24, 24, 24)}div.lz-panel-corner-boundary:hover span.lz-panel-corner-boundary-inner{border-bottom:13px solid #d8d8d8}div.lz-panel-corner-boundary:active span.lz-panel-corner-boundary-inner{border-bottom:13px solid #a6a6a6}/*# sourceMappingURL=locuszoom.css.map */ diff --git a/dist/locuszoom.css.map b/dist/locuszoom.css.map index 9ff48e62..f81e376f 100644 --- a/dist/locuszoom.css.map +++ b/dist/locuszoom.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../css/locuszoom.scss"],"names":[],"mappings":"AAYA,iBAEE,sBACA,YACA,iBACA,wDACA,eAEA,mCACE,UACA,eACA,eAIA,sCACE,aACA,iBAEF,8DACE,UACA,YAEF,gEACE,WACA,WAIJ,iCACE,eACA,gBAGF,sCACE,UACA,iBAGF,8DAEC,UACA,uBACA,iBACA,2BAGD,oCACE,eACA,gBAGF,kCACE,qBACA,kBACA,sBACA,mBAGF,uCACE,aACA,eACA,uBACD,iBACC,iBAGF,kDACE,kCACA,4BACA,4BAGF,qDACE,kCACA,8BACA,4BAGF,iGACE,uBACA,mBACA,iBACA,eAGF,yHACE,uBACA,mBACA,iBAGF,mHACE,uBACA,iBACA,iBAGF,6GACE,gBACA,kBAGF,+GACE,aAGF,6GACE,qBACA,eACA,0BAGF,6GACE,uBACA,iBACA,iBAGF,qDACE,eAGF,+HACE,iBAGF,yCACE,uBACA,iBACA,iBACA,eAGF,+CACE,kBAGF,gDACE,aAGF,iDACE,UACA,YACA,eAGF,uCACE,eAGF,6CACE,WAGF,8CACE,aAGF,mDACE,kBAGF,yEACE,aACA,eACA,iBAGF,qFACE,aACA,gBACA,iBAGF,kFACE,aACA,iBACA,uBACA,iBACA,iBAGF,sDACE,iBACA,iBAGF,kDACE,iBACA,iBAGF,iDACE,WAGF,kDACE,aAGF,iFACE,aACA,eACA,iBAGF,6FACE,aACA,gBACA,iBAGF,0FACE,aACA,iBACA,uBACA,iBACA,iBAGF,2CACE,iBAGF,iHACE,uBACA,iBACA,iBAGF,2CACE,uBACA,mBACA,iBACA,eAGF,uDACE,uBACA,mBACA,iBAGF,oDACE,uBACA,iBACA,iBAGF,iDACE,gBACA,kBAGF,kDACE,aAGF,4DACE,uBACA,iBACA,iBAMJ,0BACE,uDACA,eACA,kBACA,YACA,mBACA,iCACA,kBACA,6CAEA,8KACE,gBACA,mBAGF,4CACA,4CACA,4CACA,4CACA,4CACA,4CAEA,gCACE,cACA,yBACA,mBACA,mBAEF,kFACE,iBAEF,0DACE,YACA,gBACA,gCAGF,iCACE,qBACA,YACA,oBACA,4BACA,yBACA,sBACA,iCACA,kBACA,oBACA,aAEF,uCACE,eACA,iCACA,yBAGF,mDACE,4BACA,uCACA,sCACA,YACA,kBACA,SACA,WACA,uBAKJ,mCACE,QACA,SACA,oBACA,kCACA,mCACA,wCAGF,qCACE,QACA,SACA,oBACA,kCACA,mCACA,qCAGF,sCACE,QACA,SACA,oBACA,iCACA,oCACA,sCAGF,qCACE,QACA,SACA,oBACA,iCACA,oCACA,uCAGF,yCACE,QACA,SACA,oBACA,qCACA,sCACA,oCACA,mCAGF,0CACE,QACA,SACA,oBACA,qCACA,kCACA,oCACA,uCAGF,4CACE,QACA,SACA,oBACA,iCACA,sCACA,wCACA,mCAGF,6CACE,QACA,SACA,oBACA,iCACA,kCACA,wCACA,uCAGF,cACE,qBACA,gBAGF,yBACE,WACA,qBACA,gBAGF,YAEE,kBACA,wDACA,eACA,gBACA,gCAEA,gCACE,kBACA,cACA,WACA,QACA,2BACA,cACA,iBACA,gBAGF,gCACE,kBACA,QACA,UACA,gBACA,eACA,gBACA,yBACA,WACA,sBACA,8BACA,oBACA,eAGF,sCACE,sBACA,cAKJ,WAEE,kBACA,uDACA,eACA,YACA,mBACA,iCACA,kBACA,6CAEA,8BACE,kBACA,cACA,WAGF,yCACE,kBACA,cACA,WACA,WACA,gBAGF,+BACE,kBACA,QACA,SACA,WACA,uCAGF,wCACE,iCACA,wBACA,mCACA,sCAKJ,6BACE,oBACA,uBACA,yBAGF,iBACE,iBACA,kBACA,gBACA,mBAEA,oCACE,qBACA,wBACA,YACA,oBACA,kBACA,oBACA,aACA,mBACA,eACA,sBACA,uBACA,kBAGF,yFACA,+GACA,wHAIF,kCACE,WACA,mBAGF,mCACE,YACA,kBAGF,yCACE,4BAGF,0CACE,2BACA,4BAGF,uCACE,2BAGF,kBACE,iBACA,kBACA,gBACA,mBAEA,qCACE,qBACA,wBACA,YACA,oBACA,kBACA,oBACA,aACA,mBACA,eACA,sBACA,uBACA,kBAGF,0FACA,gHACA,yHAEA,wBACE,mBAIJ,mCACE,WACA,iBAGF,oCACE,YACA,gBAGF,YAEE,uDACA,cAEA,8BACE,mBACA,iCACE,eACA,iBACA,WAEF,oCACE,cACA,mBAMN,oBACE,uDACA,eACA,kBACA,YACA,kBACA,6CACA,sBACA,gBAEA,6CACE,cACA,WACA,YACA,gBAEA,gSACE,gBACA,mBAGF,+DACA,+DACA,+DACA,+DACA,+DACA,+DAEA,gEACE,qBACA,wBACA,YACA,oBACA,kBACA,oBACA,aACA,mBACA,eACA,sBACA,uBACA,kBACA,gBACA,iBACA,oBACA,SAGF,qHACA,2IACA,oJAIF,0BACE,cACA,yBACA,mBACA,mBACA,WAEF,sEACE,mBAEF,sCACE,iCAEF,8CACE,YACA,gBACA,sBACA,YACA,cAIJ,wBACE,sCACA,mCACA,wBAEF,6GACE,yBACA,sBACA,WAEF,oCACE,sBACA,sBACA,yBAEF,iCACE,sCACA,sCACA,2BACA,oBACA,YAGF,uBACE,sCACA,mCACA,wBAEF,0GACE,yBACA,yBACA,cAEF,mCACE,yBACA,yBACA,yBAEF,gCACE,sCACA,sCACA,2BACA,oBACA,YAGF,0BACE,sCACA,mCACA,wBAEF,mHACE,yBACA,yBACA,cAEF,sCACE,yBACA,yBACA,yBAEF,mCACE,sCACA,sCACA,2BACA,oBACA,YAGF,0BACE,sCACA,mCACA,wBAEF,mHACE,yBACA,yBACA,cAEF,sCACE,yBACA,yBACA,yBAEF,mCACE,sCACA,sCACA,2BACA,oBACA,YAGF,yBACE,sCACA,mCACA,wBAEF,gHACE,yBACA,yBACA,cAEF,qCACE,yBACA,yBACA,yBAEF,kCACE,sCACA,sCACA,2BACA,oBACA,YAGF,wBACE,sCACA,mCACA,wBAEF,6GACE,yBACA,yBACA,cAEF,oCACE,yBACA,yBACA,yBAEF,iCACE,sCACA,sCACA,2BACA,oBACA,YAGF,0BACE,sCACA,mCACA,wBAEF,mHACE,yBACA,yBACA,cAEF,sCACE,yBACA,yBACA,yBAEF,mCACE,sCACA,sCACA,2BACA,oBACA,YAGF,sBACE,yBACA,sBAGF,qBACE,mBACA,yBAGF,wBACE,mBACA,yBAGF,wBACE,mBACA,yBAGF,uBACE,mBACA,yBAGF,sBACE,mBACA,yBAGF,wBACE,mBACA,yBAGF,sBACE,kBACA,WACA,kBACA,cACA,iBACA,oBAGF,2BACE,cACA,kBACA,+BACA,qCACA,WAGF,iCACE,mBACA,iCAGF,kCACE,mBACA,iCAGF,6BACE,kBACA,YACA,WACA,mBACA,cACA,aAGF,iEACE,cACA,UACA,WACA,qCACA,mCACA,kBACA,SACA,UAGF,iEACE,cACA,UACA,WACA,qCACA,mCACA,kBACA,SACA,UAGF,uEACE,yCAGF,uEACE,iCAGF,wEACE","file":"locuszoom.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../css/locuszoom.scss"],"names":[],"mappings":"AAYA,iBAEE,sBACA,YACA,iBACA,wDACA,eAEA,mCACE,UACA,eACA,eAIA,sCACE,aACA,iBAEF,8DACE,UACA,YAEF,gEACE,WACA,WAIJ,iCACE,eACA,gBAGF,sCACE,UACA,iBAGF,8DAEC,UACA,uBACA,iBACA,2BAGD,0BAEE,eAGF,oCACE,eACA,gBAGF,uBACE,eAGF,kCACE,qBACA,kBACA,sBACA,mBAGF,uCACE,aACA,eACA,uBACD,iBACC,iBAGF,kDACE,kCACA,4BACA,4BAGF,qDACE,kCACA,8BACA,4BAGF,iGACE,uBACA,mBACA,kBACA,eAGF,yHACE,uBACA,mBACA,iBAGF,mHACE,uBACA,iBACA,iBAGF,6GACE,gBACA,kBAGF,+GACE,aAGF,6GACE,qBACA,eACA,0BAGF,6GACE,uBACA,iBACA,iBAGF,qDACE,eAGF,+HACE,iBAGF,yCACE,uBACA,iBACA,iBACA,eAGF,+CACE,kBAGF,gDACE,aAGF,iDACE,UACA,YACA,eAGF,uCACE,eAGF,6CACE,WAGF,8CACE,aAGF,mDACE,kBAGF,yEACE,aACA,eACA,iBAGF,qFACE,aACA,gBACA,iBAGF,kFACE,aACA,iBACA,uBACA,iBACA,iBAGF,sDACE,iBACA,iBAGF,kDACE,iBACA,iBAGF,iDACE,WAGF,kDACE,aAGF,iFACE,aACA,eACA,iBAGF,6FACE,aACA,gBACA,iBAGF,0FACE,aACA,iBACA,uBACA,iBACA,iBAGF,2CACE,iBAGF,iHACE,uBACA,iBACA,iBAGF,2CACE,uBACA,mBACA,iBACA,eAGF,uDACE,uBACA,mBACA,iBAGF,oDACE,uBACA,iBACA,iBAGF,iDACE,gBACA,kBAGF,kDACE,aAGF,4DACE,uBACA,iBACA,iBAMJ,0BACE,uDACA,eACA,kBACA,YACA,mBACA,iCACA,kBACA,6CAEA,8KACE,gBACA,mBAGF,4CACA,4CACA,4CACA,4CACA,4CACA,4CAEA,gCACE,cACA,yBACA,mBACA,mBAEF,kFACE,iBAEF,0DACE,YACA,gBACA,gCAGF,iCACE,qBACA,YACA,oBACA,4BACA,yBACA,sBACA,iCACA,kBACA,oBACA,aAEF,uCACE,eACA,iCACA,yBAGF,mDACE,4BACA,uCACA,sCACA,YACA,kBACA,SACA,WACA,uBAKJ,mCACE,QACA,SACA,oBACA,kCACA,mCACA,wCAGF,qCACE,QACA,SACA,oBACA,kCACA,mCACA,qCAGF,sCACE,QACA,SACA,oBACA,iCACA,oCACA,sCAGF,qCACE,QACA,SACA,oBACA,iCACA,oCACA,uCAGF,yCACE,QACA,SACA,oBACA,qCACA,sCACA,oCACA,mCAGF,0CACE,QACA,SACA,oBACA,qCACA,kCACA,oCACA,uCAGF,4CACE,QACA,SACA,oBACA,iCACA,sCACA,wCACA,mCAGF,6CACE,QACA,SACA,oBACA,iCACA,kCACA,wCACA,uCAGF,cACE,qBACA,gBAGF,yBACE,WACA,qBACA,gBAGF,YAEE,kBACA,wDACA,eACA,gBACA,gCAEA,gCACE,kBACA,cACA,WACA,QACA,2BACA,cACA,iBACA,gBAGF,gCACE,kBACA,QACA,UACA,gBACA,eACA,gBACA,yBACA,WACA,sBACA,8BACA,oBACA,eAGF,sCACE,sBACA,cAKJ,WAEE,kBACA,uDACA,eACA,YACA,mBACA,iCACA,kBACA,6CAEA,8BACE,kBACA,cACA,WAGF,yCACE,kBACA,cACA,WACA,WACA,gBAGF,+BACE,kBACA,QACA,SACA,WACA,uCAGF,wCACE,iCACA,wBACA,mCACA,sCAKJ,6BACE,oBACA,uBACA,yBAGF,iBACE,iBACA,kBACA,gBACA,mBAEA,oCACE,qBACA,wBACA,YACA,oBACA,kBACA,oBACA,aACA,mBACA,eACA,sBACA,uBACA,kBAGF,yFACA,+GACA,wHAIF,kCACE,WACA,mBAGF,mCACE,YACA,kBAGF,yCACE,4BAGF,0CACE,2BACA,4BAGF,uCACE,2BAGF,kBACE,iBACA,kBACA,gBACA,mBAEA,qCACE,qBACA,wBACA,YACA,oBACA,kBACA,oBACA,aACA,mBACA,eACA,sBACA,uBACA,kBAGF,0FACA,gHACA,yHAEA,wBACE,mBAIJ,mCACE,WACA,iBAGF,oCACE,YACA,gBAGF,YAEE,uDACA,cAEA,8BACE,mBACA,iCACE,eACA,iBACA,WAEF,oCACE,cACA,mBAMN,oBACE,uDACA,eACA,kBACA,YACA,kBACA,6CACA,sBACA,gBAEA,6CACE,cACA,WACA,YACA,gBAEA,gSACE,gBACA,mBAGF,+DACA,+DACA,+DACA,+DACA,+DACA,+DAEA,gEACE,qBACA,wBACA,YACA,oBACA,kBACA,oBACA,aACA,mBACA,eACA,sBACA,uBACA,kBACA,gBACA,iBACA,oBACA,SAGF,qHACA,2IACA,oJAIF,0BACE,cACA,yBACA,mBACA,mBACA,WAEF,sEACE,mBAEF,sCACE,iCAEF,8CACE,YACA,gBACA,sBACA,YACA,cAIJ,wBACE,sCACA,mCACA,wBAEF,6GACE,yBACA,sBACA,WAEF,oCACE,sBACA,sBACA,yBAEF,iCACE,sCACA,sCACA,2BACA,oBACA,YAGF,uBACE,sCACA,mCACA,wBAEF,0GACE,yBACA,yBACA,cAEF,mCACE,yBACA,yBACA,yBAEF,gCACE,sCACA,sCACA,2BACA,oBACA,YAGF,0BACE,sCACA,mCACA,wBAEF,mHACE,yBACA,yBACA,cAEF,sCACE,yBACA,yBACA,yBAEF,mCACE,sCACA,sCACA,2BACA,oBACA,YAGF,0BACE,sCACA,mCACA,wBAEF,mHACE,yBACA,yBACA,cAEF,sCACE,yBACA,yBACA,yBAEF,mCACE,sCACA,sCACA,2BACA,oBACA,YAGF,yBACE,sCACA,mCACA,wBAEF,gHACE,yBACA,yBACA,cAEF,qCACE,yBACA,yBACA,yBAEF,kCACE,sCACA,sCACA,2BACA,oBACA,YAGF,wBACE,sCACA,mCACA,wBAEF,6GACE,yBACA,yBACA,cAEF,oCACE,yBACA,yBACA,yBAEF,iCACE,sCACA,sCACA,2BACA,oBACA,YAGF,0BACE,sCACA,mCACA,wBAEF,mHACE,yBACA,yBACA,cAEF,sCACE,yBACA,yBACA,yBAEF,mCACE,sCACA,sCACA,2BACA,oBACA,YAGF,sBACE,yBACA,sBAGF,qBACE,mBACA,yBAGF,wBACE,mBACA,yBAGF,wBACE,mBACA,yBAGF,uBACE,mBACA,yBAGF,sBACE,mBACA,yBAGF,wBACE,mBACA,yBAGF,sBACE,kBACA,WACA,kBACA,cACA,iBACA,oBAGF,2BACE,cACA,kBACA,+BACA,qCACA,WAGF,iCACE,mBACA,iCAGF,kCACE,mBACA,iCAGF,6BACE,kBACA,YACA,WACA,mBACA,cACA,aAGF,iEACE,cACA,UACA,WACA,qCACA,mCACA,kBACA,SACA,UAGF,iEACE,cACA,UACA,WACA,qCACA,mCACA,kBACA,SACA,UAGF,uEACE,yCAGF,uEACE,iCAGF,wEACE","file":"locuszoom.css"} \ No newline at end of file diff --git a/esm/version.js b/esm/version.js index f0ea1679..063c08b3 100644 --- a/esm/version.js +++ b/esm/version.js @@ -1 +1 @@ -export default '0.14.0-beta.1'; +export default '0.14.0-beta.2'; From cc18ae3b2ab24f64f318b092555e89f643251aed Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Fri, 19 Nov 2021 11:22:24 -0500 Subject: [PATCH 090/100] Bump for preview release 0.14.0-beta.2 --- dist/ext/lz-aggregation-tests.min.js | 2 +- dist/ext/lz-credible-sets.min.js | 4 +- dist/ext/lz-credible-sets.min.js.map | 2 +- dist/ext/lz-dynamic-urls.min.js | 2 +- dist/ext/lz-forest-track.min.js | 2 +- dist/ext/lz-intervals-enrichment.min.js | 4 +- dist/ext/lz-intervals-enrichment.min.js.map | 2 +- dist/ext/lz-intervals-track.min.js | 4 +- dist/ext/lz-intervals-track.min.js.map | 2 +- dist/ext/lz-parsers.min.js | 2 +- dist/ext/lz-tabix-source.min.js | 2 +- dist/ext/lz-widget-addons.min.js | 2 +- dist/locuszoom.app.min.js | 4 +- dist/locuszoom.app.min.js.map | 2 +- docs/api/Panel.html | 33 ++-- docs/api/components_data_layer_base.js.html | 10 +- docs/api/components_data_layer_genes.js.html | 3 +- docs/api/components_legend.js.html | 85 +++++++++- docs/api/components_panel.js.html | 6 +- docs/api/ext_lz-credible-sets.js.html | 2 +- docs/api/ext_lz-intervals-enrichment.js.html | 8 +- docs/api/ext_lz-intervals-track.js.html | 2 +- docs/api/helpers_scalable.js.html | 4 +- docs/api/index.html | 28 ++-- docs/api/layouts_index.js.html | 46 +++--- ...le-LocusZoom_DataLayers-BaseDataLayer.html | 42 ++--- docs/api/module-LocusZoom_DataLayers.html | 155 +++++++++++++++++- docs/api/module-LocusZoom_Layouts.html | 44 ++--- docs/api/module-components_legend-Legend.html | 6 +- package-lock.json | 2 +- package.json | 2 +- 31 files changed, 373 insertions(+), 141 deletions(-) diff --git a/dist/ext/lz-aggregation-tests.min.js b/dist/ext/lz-aggregation-tests.min.js index efdf7096..e5546c05 100644 --- a/dist/ext/lz-aggregation-tests.min.js +++ b/dist/ext/lz-aggregation-tests.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.1 */ +/*! Locuszoom 0.14.0-beta.2 */ var LzAggregationTests;(()=>{"use strict";var e={d:(t,r)=>{for(var s in r)e.o(r,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:r[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>o});const r=raremetal;function s(e){const t=e.Adapters.get("BaseLZAdapter");e.DataFunctions.add("gene_plus_aggregation",((e,[t,r])=>{const s={};return r.groups.forEach((function(e){Object.prototype.hasOwnProperty.call(s,e.group)||(s[e.group]=[]),s[e.group].push(e.pvalue)})),t.forEach((e=>{const t=e.gene_name,r=s[t];r&&(e.aggregation_best_pvalue=Math.min.apply(null,r))})),t})),e.Adapters.add("AggregationTestSourceLZ",class extends t{constructor(e){e.prefix_namespace=!1,super(e)}_buildRequestOptions(e){const{aggregation_tests:t={}}=e,{genoset_id:r=null,genoset_build:s=null,phenoset_build:o=null,pheno:n=null,calcs:a={},masks:u=[]}=t;return t.mask_ids=u.map((e=>e.name)),e.aggregation_tests=t,e}_getURL(e){return this._url}_getCacheKey(e){const{chr:t,start:r,end:s,aggregation_tests:o}=e,{genoset_id:n=null,genoset_build:a=null,phenoset_id:u=null,pheno:l=null,mask_ids:g}=o;return JSON.stringify({chrom:t,start:r,stop:s,genotypeDataset:n,phenotypeDataset:u,phenotype:l,samples:"ALL",genomeBuild:a,masks:g})}_performRequest(e){const t=this._getURL(e),r=this._getCacheKey(e);return fetch(t,{method:"POST",body:r,headers:{"Content-Type":"application/json"}}).then((e=>{if(!e.ok)throw new Error(e.statusText);return e.text()})).then((e=>{const t="string"==typeof e?JSON.parse(e):e;if(t.error)throw new Error(t.error);return t.data}))}_annotateRecords(e,t){const{aggregation_tests:s}=t,{calcs:o=[],mask_ids:n=[],masks:a=[]}=s;if(!e.groups)return{groups:[],variants:[]};e.groups=e.groups.filter((e=>"GENE"===e.groupType));const u=r.helpers.parsePortalJSON(e);let l=u[0];const g=u[1];if(l=l.byMask(n),!o||0===Object.keys(o).length)return{variants:[],groups:[],results:[]};return new r.helpers.PortalTestRunner(l,g,o).toJSON().then((function(e){const t=a.reduce(((e,t)=>(e[t.name]=t.description,e)),{});return e.data.groups.forEach((e=>{e.mask_name=t[e.mask]})),e.data})).catch((function(e){throw console.error(e),new Error("Failed to calculate aggregation test results")}))}}),e.Adapters.add("AssocFromAggregationLZ",class extends t{_buildRequestOptions(e,t){if(!t)throw new Error("Aggregation test results must be provided");return e._agg_results=t,e}_performRequest(e){return Promise.resolve(e._agg_results.variants)}_normalizeResponse(e){const t=new RegExp("(?:chr)?(.+):(\\d+)_?(\\w+)?/?([^_]+)?_?(.*)?");return e.map((e=>{const{variant:r,altFreq:s,pvalue:o}=e,n=r.match(t),[a,u,l,g]=n;return{variant:r,chromosome:u,position:+l,ref_allele:g,ref_allele_freq:1-s,log_pvalue:-Math.log10(o)}})).sort(((e,t)=>(e=e.variant)<(t=t.variant)?-1:e>t?1:0))}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(s);const o=s;LzAggregationTests=t.default})(); //# sourceMappingURL=lz-aggregation-tests.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-credible-sets.min.js b/dist/ext/lz-credible-sets.min.js index 2a62debf..8819534f 100644 --- a/dist/ext/lz-credible-sets.min.js +++ b/dist/ext/lz-credible-sets.min.js @@ -1,4 +1,4 @@ -/*! Locuszoom 0.14.0-beta.1 */ +/*! Locuszoom 0.14.0-beta.2 */ var LzCredibleSets;(()=>{var e={803:function(e){var t;t=function(){return function(e){var t={};function r(o){if(t[o])return t[o].exports;var a=t[o]={i:o,l:!1,exports:{}};return e[o].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(e,t,r){"use strict"; /** * @module stats @@ -12,5 +12,5 @@ function o(e){var t=[3.3871328727963665,133.14166789178438,1971.5909503065513,13 /** * @module marking * @license MIT - */function a(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:.95;if(!Array.isArray(e)||!e.length)throw"Probs must be a non-empty array";if("number"!=typeof t||t<0||t>1||Number.isNaN(t))throw"Cutoff must be a number between 0 and 1";var r=e.reduce((function(e,t){return e+t}),0);if(r<=0)throw"Sum of provided probabilities must be > 0";for(var a=e.map((function(e,t){return[e,t]})).sort((function(e,t){return t[0]-e[0]})),i=0,n=new Array(a.length).fill(0),s=0;s{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var o={};(()=>{"use strict";r.d(o,{default:()=>a});var e=r(803);function t(t){const r=t.Adapters.get("BaseUMAdapter");t.Adapters.add("CredibleSetLZ",class extends r{constructor(e){super(...arguments),this._config=Object.assign({threshold:.95,significance_threshold:7.301},this._config),this._prefix_namespace=!1}_getCacheKey(e){return[e.credible_set_threshold||this._config.threshold,e.chr,e.start,e.end].join("_")}_buildRequestOptions(e,t){const r=super._buildRequestOptions(...arguments);return r._assoc_data=t,r}_performRequest(t){const{_assoc_data:r}=t;if(!r.length)return Promise.resolve([]);const o=this._findPrefixedKey(r[0],"log_pvalue"),a=this._config.threshold,i=r.map((e=>e[o]));if(!i.some((e=>e>=this._config.significance_threshold)))return Promise.resolve(r);try{const o=e.scoring.bayesFactors(i),n=e.scoring.normalizeProbabilities(o),s=e.marking.findCredibleSet(n,a),l=e.marking.rescaleCredibleSet(s),c=e.marking.markBoolean(s);for(let e=0;e{{assoc:variant|htmlescape}}
    P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    {{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}"});const a=function(){const e=t.Layouts.get("data_layer","association_pvalues",{id:"associationcredibleset",namespace:{assoc:"assoc",credset:"credset",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)","credset(assoc)"]},{type:"left_match",name:"credset_plus_ld",requires:["credset","ld"],params:["assoc:position","ld:position2"]}],fill_opacity:.7,tooltip:t.Layouts.get("tooltip","association_credible_set"),match:{send:"assoc:variant",receive:"assoc:variant"}});return e.color.unshift({field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#FFf000"}}),e}();t.Layouts.add("data_layer","association_credible_set",a);const i={namespace:{assoc:"assoc",credset:"credset"},data_operations:[{type:"fetch",from:["assoc","credset(assoc)"]}],id:"annotationcredibleset",type:"annotation_track",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#001cee"}},"#00CC00"],match:{send:"assoc:variant",receive:"assoc:variant"},filters:[{field:"credset:is_member",operator:"=",value:!0}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:t.Layouts.get("tooltip","annotation_credible_set"),tooltip_positioning:"top"};t.Layouts.add("data_layer","annotation_credible_set",i);const n={id:"annotationcredibleset",title:{text:"SNPs in 95% credible set",x:50,style:{"font-size":"14px"}},min_height:50,height:50,margin:{top:25,right:50,bottom:10,left:50},inner_border:"rgb(210, 210, 210)",toolbar:t.Layouts.get("toolbar","standard_panel"),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[t.Layouts.get("data_layer","annotation_credible_set")]};t.Layouts.add("panel","annotation_credible_set",n);const s=function(){const e=t.Layouts.get("panel","association",{id:"associationcrediblesets",data_layers:[t.Layouts.get("data_layer","significance"),t.Layouts.get("data_layer","recomb_rate"),t.Layouts.get("data_layer","association_credible_set")]});return e.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationcredibleset",default_config_display_name:"Linkage Disequilibrium (default)",options:[{display_name:"95% credible set (boolean)",display:{point_shape:"circle",point_size:40,color:{field:"credset:is_member",scale_function:"if",parameters:{field_value:!0,then:"#00CC00",else:"#CCCCCC"}},legend:[{shape:"circle",color:"#00CC00",size:40,label:"In credible set",class:"lz-data_layer-scatter"},{shape:"circle",color:"#CCCCCC",size:40,label:"Not in credible set",class:"lz-data_layer-scatter"}]}},{display_name:"95% credible set (gradient by contribution)",display:{point_shape:"circle",point_size:40,color:[{field:"credset:contrib_fraction",scale_function:"if",parameters:{field_value:0,then:"#777777"}},{scale_function:"interpolate",field:"credset:contrib_fraction",parameters:{breaks:[0,1],values:["#fafe87","#9c0000"]}}],legend:[{shape:"circle",color:"#777777",size:40,label:"No contribution",class:"lz-data_layer-scatter"},{shape:"circle",color:"#fafe87",size:40,label:"Some contribution",class:"lz-data_layer-scatter"},{shape:"circle",color:"#9c0000",size:40,label:"Most contribution",class:"lz-data_layer-scatter"}]}}]}),e}();t.Layouts.add("panel","association_credible_set",s);const l={state:{},width:800,height:450,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association"),panels:[t.Layouts.get("panel","association_credible_set"),t.Layouts.get("panel","annotation_credible_set"),t.Layouts.get("panel","genes")]};t.Layouts.add("plot","association_credible_set",l)}"undefined"!=typeof LocusZoom&&LocusZoom.use(t);const a=t})(),LzCredibleSets=o.default})(); + */function a(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:.95;if(!Array.isArray(e)||!e.length)throw"Probs must be a non-empty array";if("number"!=typeof t||t<0||t>1||Number.isNaN(t))throw"Cutoff must be a number between 0 and 1";var r=e.reduce((function(e,t){return e+t}),0);if(r<=0)throw"Sum of provided probabilities must be > 0";for(var a=e.map((function(e,t){return[e,t]})).sort((function(e,t){return t[0]-e[0]})),i=0,n=new Array(a.length).fill(0),s=0;s{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var o={};(()=>{"use strict";r.d(o,{default:()=>a});var e=r(803);function t(t){const r=t.Adapters.get("BaseUMAdapter");t.Adapters.add("CredibleSetLZ",class extends r{constructor(e){super(...arguments),this._config=Object.assign({threshold:.95,significance_threshold:7.301},this._config),this._prefix_namespace=!1}_getCacheKey(e){return[e.credible_set_threshold||this._config.threshold,e.chr,e.start,e.end].join("_")}_buildRequestOptions(e,t){const r=super._buildRequestOptions(...arguments);return r._assoc_data=t,r}_performRequest(t){const{_assoc_data:r}=t;if(!r.length)return Promise.resolve([]);const o=this._findPrefixedKey(r[0],"log_pvalue"),a=this._config.threshold,i=r.map((e=>e[o]));if(!i.some((e=>e>=this._config.significance_threshold)))return Promise.resolve(r);try{const o=e.scoring.bayesFactors(i),n=e.scoring.normalizeProbabilities(o),s=e.marking.findCredibleSet(n,a),l=e.marking.rescaleCredibleSet(s),c=e.marking.markBoolean(s);for(let e=0;e{{assoc:variant|htmlescape}}
    P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    {{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}"});const a=function(){const e=t.Layouts.get("data_layer","association_pvalues",{id:"associationcredibleset",namespace:{assoc:"assoc",credset:"credset",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)","credset(assoc)"]},{type:"left_match",name:"credset_plus_ld",requires:["credset","ld"],params:["assoc:position","ld:position2"]}],fill_opacity:.7,tooltip:t.Layouts.get("tooltip","association_credible_set"),match:{send:"assoc:variant",receive:"assoc:variant"}});return e.color.unshift({field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#FFf000"}}),e}();t.Layouts.add("data_layer","association_credible_set",a);const i={namespace:{assoc:"assoc",credset:"credset"},data_operations:[{type:"fetch",from:["assoc","credset(assoc)"]}],id:"annotationcredibleset",type:"annotation_track",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#001cee"}},"#00CC00"],match:{send:"assoc:variant",receive:"assoc:variant"},filters:[{field:"credset:is_member",operator:"=",value:!0}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:t.Layouts.get("tooltip","annotation_credible_set"),tooltip_positioning:"top"};t.Layouts.add("data_layer","annotation_credible_set",i);const n={id:"annotationcredibleset",title:{text:"SNPs in 95% credible set",x:50,style:{"font-size":"14px"}},min_height:50,height:50,margin:{top:25,right:50,bottom:10,left:70},inner_border:"rgb(210, 210, 210)",toolbar:t.Layouts.get("toolbar","standard_panel"),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[t.Layouts.get("data_layer","annotation_credible_set")]};t.Layouts.add("panel","annotation_credible_set",n);const s=function(){const e=t.Layouts.get("panel","association",{id:"associationcrediblesets",data_layers:[t.Layouts.get("data_layer","significance"),t.Layouts.get("data_layer","recomb_rate"),t.Layouts.get("data_layer","association_credible_set")]});return e.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationcredibleset",default_config_display_name:"Linkage Disequilibrium (default)",options:[{display_name:"95% credible set (boolean)",display:{point_shape:"circle",point_size:40,color:{field:"credset:is_member",scale_function:"if",parameters:{field_value:!0,then:"#00CC00",else:"#CCCCCC"}},legend:[{shape:"circle",color:"#00CC00",size:40,label:"In credible set",class:"lz-data_layer-scatter"},{shape:"circle",color:"#CCCCCC",size:40,label:"Not in credible set",class:"lz-data_layer-scatter"}]}},{display_name:"95% credible set (gradient by contribution)",display:{point_shape:"circle",point_size:40,color:[{field:"credset:contrib_fraction",scale_function:"if",parameters:{field_value:0,then:"#777777"}},{scale_function:"interpolate",field:"credset:contrib_fraction",parameters:{breaks:[0,1],values:["#fafe87","#9c0000"]}}],legend:[{shape:"circle",color:"#777777",size:40,label:"No contribution",class:"lz-data_layer-scatter"},{shape:"circle",color:"#fafe87",size:40,label:"Some contribution",class:"lz-data_layer-scatter"},{shape:"circle",color:"#9c0000",size:40,label:"Most contribution",class:"lz-data_layer-scatter"}]}}]}),e}();t.Layouts.add("panel","association_credible_set",s);const l={state:{},width:800,height:450,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association"),panels:[t.Layouts.get("panel","association_credible_set"),t.Layouts.get("panel","annotation_credible_set"),t.Layouts.get("panel","genes")]};t.Layouts.add("plot","association_credible_set",l)}"undefined"!=typeof LocusZoom&&LocusZoom.use(t);const a=t})(),LzCredibleSets=o.default})(); //# sourceMappingURL=lz-credible-sets.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-credible-sets.min.js.map b/dist/ext/lz-credible-sets.min.js.map index 1cf01178..47419145 100644 --- a/dist/ext/lz-credible-sets.min.js.map +++ b/dist/ext/lz-credible-sets.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/../../../../../webpack/universalModuleDefinition","webpack://[name]/../../../../../webpack/bootstrap 069b89435249357eaca3","webpack://[name]/../../../../../src/app/stats.js","webpack://[name]/../../../../../src/app/gwas-credible-sets.js","webpack://[name]/../../../../../src/app/scoring.js","webpack://[name]/../../../../../src/app/marking.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-credible-sets.js"],"names":["factory","module","Object","prototype","hasOwnProperty","call","object","property","ninv","p","a","b","c","d","e","f","q","r","x","Math","abs","sqrt","log","rollup","scoring","stats","marking","_nlogp_to_z2","nlogp","pow","bayesFactors","nlogpvals","cap","Array","isArray","length","z2_2","map","item","capValue","max","exp","normalizeProbabilities","scores","sumValues","reduce","findCredibleSet","probs","cutoff","Number","isNaN","statsTotal","sortedStatsMap","index","sort","runningTotal","result","fill","i","value","score","markBoolean","credibleSetMembers","rescaleCredibleSet","sumMarkers","exports","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","definition","key","o","defineProperty","enumerable","get","obj","prop","install","LocusZoom","BaseUMAdapter","Adapters","add","config","super","arguments","this","_config","assign","threshold","significance_threshold","_prefix_namespace","state","credible_set_threshold","chr","start","end","join","options","assoc_data","base","_buildRequestOptions","_assoc_data","Promise","resolve","assoc_logp_name","_findPrefixedKey","some","val","posteriorProbabilities","credibleSet","credSetScaled","credSetBool","_provider_name","console","error","association_credible_set_tooltip","l","Layouts","html","closable","show","or","hide","and","association_credible_set_layer","id","namespace","data_operations","type","from","name","requires","params","fill_opacity","tooltip","match","send","receive","color","unshift","field","scale_function","parameters","field_value","then","annotation_credible_set_layer","id_field","x_axis","filters","operator","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip_positioning","annotation_credible_set","title","text","style","min_height","height","margin","top","right","bottom","left","inner_border","toolbar","axes","extent","render","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","data_layers","association_credible_set_panel","widgets","push","position","button_html","button_title","layer_name","default_config_display_name","display_name","display","point_shape","point_size","else","legend","shape","size","label","class","breaks","values","association_credible_set_plot","width","responsive_resize","min_region_scale","max_region_scale","panels","use"],"mappings":"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","file":"ext/lz-credible-sets.min.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"gwasCredibleSets\"] = factory();\n\telse\n\t\troot[\"gwasCredibleSets\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 069b89435249357eaca3","/** \n * @module stats \n * @license MIT\n * */\n\n/**\n * The inverse of the standard normal CDF. May be used to determine the Z-score for the desired quantile.\n *\n * This is an implementation of algorithm AS241:\n * https://www.jstor.org/stable/2347330\n * \n * @param {number} p The desired quantile of the standard normal distribution.\n * @returns {number}\n */\nfunction ninv(p) {\n const SPLIT1 = 0.425;\n const SPLIT2 = 5.0;\n const CONST1 = 0.180625;\n const CONST2 = 1.6;\n const a = [\n 3.3871328727963666080E0,\n 1.3314166789178437745E2,\n 1.9715909503065514427E3,\n 1.3731693765509461125E4,\n 4.5921953931549871457E4,\n 6.7265770927008700853E4,\n 3.3430575583588128105E4,\n 2.5090809287301226727E3\n ];\n\n const b = [\n 4.2313330701600911252E1,\n 6.8718700749205790830E2,\n 5.3941960214247511077E3,\n 2.1213794301586595867E4,\n 3.9307895800092710610E4,\n 2.8729085735721942674E4,\n 5.2264952788528545610E3\n ];\n\n const c = [\n 1.42343711074968357734E0,\n 4.63033784615654529590E0,\n 5.76949722146069140550E0,\n 3.64784832476320460504E0,\n 1.27045825245236838258E0,\n 2.41780725177450611770E-1,\n 2.27238449892691845833E-2,\n 7.74545014278341407640E-4\n ];\n\n const d = [\n 2.05319162663775882187E0,\n 1.67638483018380384940E0,\n 6.89767334985100004550E-1,\n 1.48103976427480074590E-1,\n 1.51986665636164571966E-2,\n 5.47593808499534494600E-4,\n 1.05075007164441684324E-9\n ];\n\n const e = [\n 6.65790464350110377720E0,\n 5.46378491116411436990E0,\n 1.78482653991729133580E0,\n 2.96560571828504891230E-1,\n 2.65321895265761230930E-2,\n 1.24266094738807843860E-3,\n 2.71155556874348757815E-5,\n 2.01033439929228813265E-7\n ];\n\n const f = [\n 5.99832206555887937690E-1,\n 1.36929880922735805310E-1,\n 1.48753612908506148525E-2,\n 7.86869131145613259100E-4,\n 1.84631831751005468180E-5,\n 1.42151175831644588870E-7,\n 2.04426310338993978564E-15\n ];\n\n const q = p - 0.5;\n let r, x;\n\n if (Math.abs(q) < SPLIT1) {\n r = CONST1 - q * q;\n return q * ((((((( a[7] * r + a[6] ) * r + a[5] ) * r + a[4] ) * r\n + a[3] ) * r + a[2] ) * r + a[1] ) * r + a[0] ) /\n ((((((( b[6] * r + b[5] ) * r + b[4] ) * r + b[3] ) * r\n + b[2] ) * r + b[1] ) * r + b[0] ) * r + 1.0 );\n }\n else {\n if (q < 0) {\n r = p\n }\n else {\n r = 1.0 - p\n }\n\n if (r > 0) {\n r = Math.sqrt(-Math.log(r));\n if (r <= SPLIT2) {\n r -= CONST2;\n x = ((((((( c[7] * r + c[6] ) * r + c[5] ) * r + c[4] ) * r\n + c[3] ) * r + c[2] ) * r + c[1] ) * r + c[0] ) /\n ((((((( d[6] * r + d[5] ) * r + d[4] ) * r + d[3] ) * r\n + d[2] ) * r + d[1] ) * r + d[0] ) * r + 1.0 );\n }\n else {\n r -= SPLIT2;\n x = ((((((( e[7] * r + e[6] ) * r + e[5] ) * r + e[4] ) * r\n + e[3] ) * r + e[2] ) * r + e[1] ) * r + e[0] ) /\n ((((((( f[6] * r + f[5] ) * r + f[4] ) * r + f[3] ) * r\n + f[2] ) * r + f[1] ) * r + f[0] ) * r + 1.0 );\n }\n }\n else {\n throw('Not implemented')\n }\n\n if (q < 0) {\n x = -x\n }\n\n return x;\n }\n}\n\n// Hack: A single global object representing the contents of the module\nconst rollup = { ninv };\n\nexport { ninv };\nexport default rollup;\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/stats.js","/** \n * Functions for calculating credible sets and Bayes factors from\n * genome-wide association study (GWAS) results. \n * @module gwas-credible-sets \n * @license MIT\n */\n\nimport stats from './stats';\nimport scoring from './scoring';\nimport marking from './marking';\n\n// HACK: Because a primary audience is targets that do not have any module system, we will expose submodules from the\n// top-level module. (by representing each sub-module as a \"rollup object\" that exposes its internal methods)\n// Then, submodules may be accessed as `window.gwasCredibleSets.stats`, etc\n\n// If you are using a real module system, please import from sub-modules directly- these global helpers are a bit of\n// a hack and may go away in the future\nexport { scoring, stats, marking };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/gwas-credible-sets.js","/** \n * @module scoring \n * @license MIT\n */\n\nimport { ninv } from './stats';\n\n\n/**\n * Convert a -log10 p-value to Z^2.\n *\n * Very large -log10 p-values (very small p-values) cannot be converted to a Z-statistic directly in the browser due to\n * limitations in javascript (64-bit floats.) These values are handled using an approximation:\n * for small p-values, Z_i^2 has a linear relationship with -log10 p-value.\n *\n * The approximation begins for -log10 p-values >= 300.\n *\n * @param nlogp\n * @return {number}\n * @private\n */\nfunction _nlogp_to_z2(nlogp) {\n const p = Math.pow(10, -nlogp);\n if (nlogp < 300) {\n // Use exact method when within the range of 64-bit floats (approx 10^-323)\n return Math.pow(ninv(p / 2), 2);\n }\n else {\n // For very small p-values, -log10(pval) and Z^2 have a linear relationship\n // This avoids issues with needing higher precision floats when doing the calculation\n // with ninv\n return (4.59884133027944 * nlogp) - 5.88085867031722\n }\n}\n\n/**\n * Calculate a Bayes factor exp(Z^2 / 2) based on p-values. If the Z-score is very large, the Bayes factors\n * are calculated in an inexact (capped) manner that makes the calculation tractable but preserves comparisons.\n * @param {Number[]} nlogpvals An array of -log10(p-value) entries\n * @param {Boolean} [cap=true] Whether to apply an inexact method. If false, some values in the return array may\n * be represented as \"Infinity\", but the Bayes factors will be directly calculated wherever possible.\n * @return {Number[]} An array of exp(Z^2 / 2) statistics\n */\nfunction bayesFactors(nlogpvals, cap=true) {\n if (!Array.isArray(nlogpvals) || ! nlogpvals.length) {\n throw 'Must provide a non-empty array of pvalues';\n }\n\n // 1. Convert the pvalues to Z^2 / 2 values. Divide by 2 before applying the cap, because it means fewer values will\n // need to be truncated. This does affect some of the raw bayes factors that are returned (when a cap is needed),\n // but the resulting credible set contents / posterior probabilities are unchanged.\n let z2_2 = nlogpvals.map(item => _nlogp_to_z2(item) / 2);\n\n // 2. Calculate bayes factor, using a truncation approach that prevents overrunning the max float64 value\n // (when Z^2 / 2 > 709 or so). As safeguard, we could (but currently don't) check that exp(Z^2 / 2) is not larger\n // than infinity.\n if (cap) {\n const capValue = Math.max(...z2_2) - 708; // The real cap is ~709; this should prevent any value from exceeding it\n if (capValue > 0) {\n z2_2 = z2_2.map(item => (item - capValue));\n }\n }\n return z2_2.map(item => Math.exp(item));\n}\n\n/**\n * Normalize so that sum of all elements = 1.0. This method must be applied to bayes factors before calculating any\n * credible set.\n *\n * @param {Number[]} scores An array of probability scores for all elements in the range\n * @returns {Number[]} Posterior probabilities\n */\nfunction normalizeProbabilities(scores) {\n const sumValues = scores.reduce((a, b) => a + b, 0);\n return scores.map(item => item / sumValues);\n}\n\nconst rollup = { bayesFactors, normalizeProbabilities };\nexport default rollup;\nexport { bayesFactors, normalizeProbabilities };\n\n// Export additional symbols for unit testing only (not part of public interface for the module)\nexport { _nlogp_to_z2 };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/scoring.js","/**\n * @module marking\n * @license MIT\n */\n\n/**\n * Given an array of probabilities, determine which elements of the array fall within the X% credible set,\n * where X is the cutoff value.\n *\n * @param {Number[]} probs Calculated probabilities used to rank the credible set. This method will normalize the\n * provided input to ensure that the values sum to 1.0.\n * @param {Number} [cutoff=0.95] Keep taking items until we have accounted for >= this fraction of the total probability.\n * For example, 0.95 would represent the 95% credible set.\n * @return {Number[]} An array with posterior probabilities (for the items in the credible set), and zero for all\n * other elements. This array is the same length as the provided probabilities array.\n */\nfunction findCredibleSet(probs, cutoff=0.95) {\n // Type checking\n if (!Array.isArray(probs) || !probs.length) {\n throw 'Probs must be a non-empty array';\n }\n if (!(typeof cutoff === 'number' ) || cutoff < 0 || cutoff > 1.0 || Number.isNaN(cutoff)) {\n throw 'Cutoff must be a number between 0 and 1';\n }\n\n const statsTotal = probs.reduce((a, b) => a + b, 0);\n if (statsTotal <= 0) {\n throw 'Sum of provided probabilities must be > 0';\n }\n\n // Sort the probabilities by largest first, while preserving a map to original item order\n const sortedStatsMap = probs\n .map((item, index) => [item, index])\n .sort((a, b) => (b[0] - a[0]));\n\n let runningTotal = 0;\n const result = new Array(sortedStatsMap.length).fill(0);\n for (let i = 0; i < sortedStatsMap.length; i++) {\n let [value, index] = sortedStatsMap[i];\n if (runningTotal < cutoff) {\n // Convert from a raw score to posterior probability by dividing the item under consideration\n // by sum of all probabilities.\n const score = value / statsTotal;\n result[index] = score;\n runningTotal += score;\n } else {\n break;\n }\n }\n return result;\n}\n\n/**\n * Given a numeric [pre-calculated credible set]{@link #findCredibleSet}, return an array of booleans where true\n * denotes membership in the credible set.\n *\n * This is a helper method used when visualizing the members of the credible set by raw membership.\n *\n * @param {Number[]} credibleSetMembers An array indicating contributions to the credible set, where non-members are\n * represented by some falsy value.\n * @return {Boolean[]} An array of booleans identifying whether or not each item is in the credible set.\n * This array is the same length as the provided credible set array.\n */\nfunction markBoolean(credibleSetMembers) {\n return credibleSetMembers.map(item => !!item);\n}\n\n/**\n * Visualization helper method for rescaling data to a predictable output range, eg when range for a color gradient\n * must be specified in advance.\n *\n * Given an array of probabilities for items in a credible set, rescale the probabilities within only the credible\n * set to their total sum.\n *\n * Example for 95% credible set: [0.92, 0.06, 0.02] -> [0.938, 0.061, 0]. The first two elements here\n * belong to the credible set, the last element does not.\n *\n * @param {Number[]} credibleSetMembers Calculated probabilities used to rank the credible set.\n * @return {Number[]} The fraction of credible set probabilities each item accounts for.\n * This array is the same length as the provided credible set.\n */\nfunction rescaleCredibleSet(credibleSetMembers) {\n const sumMarkers = credibleSetMembers.reduce((a, b) => a + b, 0);\n return credibleSetMembers.map(item => item / sumMarkers);\n}\n\nconst rollup = { findCredibleSet, markBoolean, rescaleCredibleSet };\nexport default rollup;\nexport { findCredibleSet, markBoolean, rescaleCredibleSet };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/marking.js","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Custom code used to power credible sets demonstration example. This is not part of the core LocusZoom library,\n * but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~CredibleSetLZ}\n * * {@link module:LocusZoom_Layouts~association_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_layer}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set_plot}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import credibleSets from 'locuszoom/esm/ext/lz-credible-sets';\n * LocusZoom.use(credibleSets);\n * ```\n @module\n*/\n\nimport {marking, scoring} from 'gwas-credible-sets';\n\nfunction install (LocusZoom) {\n const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter');\n\n /**\n * (**extension**) Custom data adapter that calculates the 95% credible set based on provided association data.\n * This source must be requested as the second step in a chain, after a previous step that returns fields required\n * for the calculation. (usually, it follows a request for GWAS summary statistics)\n * @alias module:LocusZoom_Adapters~CredibleSetLZ\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n class CredibleSetLZ extends BaseUMAdapter {\n /**\n * @param {Number} [config.params.threshold=0.95] The credible set threshold (eg 95%). Will continue selecting SNPs\n * until the posterior probabilities add up to at least this fraction of the total.\n * @param {Number} [config.params.significance_threshold=7.301] Do not perform a credible set calculation for this\n * region unless AT LEAST ONE SNP (as -log10p) exceeds the line of GWAS signficance. Otherwise we are declaring a\n * credible set when there is no evidence of anything being significant at all. If one snp is significant, it will\n * create a credible set for the entire region; the resulting set may include things below the line of significance.\n */\n constructor(config) {\n super(...arguments);\n // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p)\n this._config = Object.assign(\n { threshold: 0.95, significance_threshold: 7.301 },\n this._config\n );\n this._prefix_namespace = false;\n }\n\n _getCacheKey (state) {\n const threshold = state.credible_set_threshold || this._config.threshold;\n return [threshold, state.chr, state.start, state.end].join('_');\n }\n\n _buildRequestOptions(options, assoc_data) {\n const base = super._buildRequestOptions(...arguments);\n base._assoc_data = assoc_data;\n return base;\n }\n\n _performRequest(options) {\n const {_assoc_data} = options;\n if (!_assoc_data.length) {\n // No credible set can be calculated because there is no association data for this region\n return Promise.resolve([]);\n }\n\n const assoc_logp_name = this._findPrefixedKey(_assoc_data[0], 'log_pvalue');\n\n const threshold = this._config.threshold;\n\n // Calculate raw bayes factors and posterior probabilities based on information returned from the API\n const nlogpvals = _assoc_data.map((item) => item[assoc_logp_name]);\n\n if (!nlogpvals.some((val) => val >= this._config.significance_threshold)) {\n // If NO points have evidence of significance, define the credible set to be empty\n // (rather than make a credible set that we don't think is meaningful)\n return Promise.resolve(_assoc_data);\n }\n\n try {\n const scores = scoring.bayesFactors(nlogpvals);\n const posteriorProbabilities = scoring.normalizeProbabilities(scores);\n\n // Use scores to mark the credible set in various ways (depending on your visualization preferences,\n // some of these may not be needed)\n const credibleSet = marking.findCredibleSet(posteriorProbabilities, threshold);\n const credSetScaled = marking.rescaleCredibleSet(credibleSet);\n const credSetBool = marking.markBoolean(credibleSet);\n\n // Annotate each response record based on credible set membership. This has the effect of joining\n // credset results to assoc data directly within the adapter (no separate join needed)\n for (let i = 0; i < _assoc_data.length; i++) {\n _assoc_data[i][`${options._provider_name}:posterior_prob`] = posteriorProbabilities[i];\n _assoc_data[i][`${options._provider_name}:contrib_fraction`] = credSetScaled[i];\n _assoc_data[i][`${options._provider_name}:is_member`] = credSetBool[i];\n }\n } catch (e) {\n // If the calculation cannot be completed, return the data without annotation fields\n console.error(e);\n }\n return Promise.resolve(_assoc_data);\n }\n }\n\n LocusZoom.Adapters.add('CredibleSetLZ', CredibleSetLZ);\n\n // Add related layouts to the central global registry\n /**\n * (**extension**) Tooltip layout that appends credible set posterior probability to the default association tooltip (for SNPs in the credible set)\n * @alias module:LocusZoom_Layouts~association_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_tooltip = function () {\n // Extend a known tooltip with an extra row of info showing posterior probabilities\n const l = LocusZoom.Layouts.get('tooltip', 'standard_association');\n l.html += '{{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}';\n return l;\n }();\n\n LocusZoom.Layouts.add('tooltip', 'association_credible_set', association_credible_set_tooltip);\n\n /**\n * (**extension**) A tooltip layout for annotation (rug) tracks that provides information about credible set members\n * @alias module:LocusZoom_Layouts~annotation_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{assoc:variant|htmlescape}}
    '\n + 'P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    ' +\n '{{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}',\n };\n LocusZoom.Layouts.add('tooltip', 'annotation_credible_set', annotation_credible_set_tooltip);\n\n /**\n * (**extension**) A data layer layout that shows GWAS summary statistics overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n\n const association_credible_set_layer = function () {\n const base = LocusZoom.Layouts.get('data_layer', 'association_pvalues', {\n id: 'associationcredibleset',\n namespace: { 'assoc': 'assoc', 'credset': 'credset', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)', 'credset(assoc)'],\n },\n {\n type: 'left_match',\n name: 'credset_plus_ld',\n requires: ['credset', 'ld'], // The credible sets demo wasn't fully moved over to the new data operations system, and as such it is a bit weird\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n fill_opacity: 0.7,\n tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set'),\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n });\n base.color.unshift({\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#FFf000',\n },\n });\n return base;\n }();\n LocusZoom.Layouts.add('data_layer', 'association_credible_set', association_credible_set_layer);\n\n /**\n * (**extension**) A data layer layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_layer = {\n namespace: { 'assoc': 'assoc', 'credset': 'credset' },\n data_operations: [{\n type: 'fetch',\n from: ['assoc', 'credset(assoc)'],\n }],\n id: 'annotationcredibleset',\n type: 'annotation_track',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#001cee',\n },\n },\n '#00CC00',\n ],\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'credset:is_member', operator: '=', value: true },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: LocusZoom.Layouts.get('tooltip', 'annotation_credible_set'),\n tooltip_positioning: 'top',\n };\n LocusZoom.Layouts.add('data_layer', 'annotation_credible_set', annotation_credible_set_layer);\n\n /**\n * (**extension**) A panel layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set = {\n id: 'annotationcredibleset',\n title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } },\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 50, bottom: 10, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel'),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'annotation_credible_set'),\n ],\n };\n LocusZoom.Layouts.add('panel', 'annotation_credible_set', annotation_credible_set);\n\n /**\n * (**extension**) A panel layout that shows GWAS summary statistics in a standard LocusZoom view, overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_panel = function () {\n const l = LocusZoom.Layouts.get('panel', 'association', {\n id: 'associationcrediblesets',\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'significance'),\n LocusZoom.Layouts.get('data_layer', 'recomb_rate'),\n LocusZoom.Layouts.get('data_layer', 'association_credible_set'),\n ],\n });\n // Add \"display options\" button to control how credible set coloring is overlaid on the standard association plot\n l.toolbar.widgets.push(\n {\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n layer_name: 'associationcredibleset',\n default_config_display_name: 'Linkage Disequilibrium (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: '95% credible set (boolean)', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n point_shape: 'circle',\n point_size: 40,\n color: {\n field: 'credset:is_member',\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#00CC00',\n else: '#CCCCCC',\n },\n },\n legend: [ // Tells the legend how to represent this display option\n {\n shape: 'circle',\n color: '#00CC00',\n size: 40,\n label: 'In credible set',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#CCCCCC',\n size: 40,\n label: 'Not in credible set',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n {\n // Second option. The same plot- or even the same field- can be colored in more than one way.\n display_name: '95% credible set (gradient by contribution)',\n display: {\n point_shape: 'circle',\n point_size: 40,\n color: [\n {\n field: 'credset:contrib_fraction',\n scale_function: 'if',\n parameters: {\n field_value: 0,\n then: '#777777',\n },\n },\n {\n scale_function: 'interpolate',\n field: 'credset:contrib_fraction',\n parameters: {\n breaks: [0, 1],\n values: ['#fafe87', '#9c0000'],\n },\n },\n ],\n legend: [\n {\n shape: 'circle',\n color: '#777777',\n size: 40,\n label: 'No contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#fafe87',\n size: 40,\n label: 'Some contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#9c0000',\n size: 40,\n label: 'Most contribution',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n ],\n }\n );\n return l;\n }();\n LocusZoom.Layouts.add('panel', 'association_credible_set', association_credible_set_panel);\n\n /**\n * (**extension**) A standard LocusZoom plot layout, with additional credible set information.\n * @alias module:LocusZoom_Layouts~association_credible_set_plot\n * @type plot\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_plot = {\n state: {},\n width: 800,\n height: 450,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n LocusZoom.Layouts.get('panel', 'association_credible_set'),\n LocusZoom.Layouts.get('panel', 'annotation_credible_set'),\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n LocusZoom.Layouts.add('plot', 'association_credible_set', association_credible_set_plot);\n}\n\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/../../../../../webpack/universalModuleDefinition","webpack://[name]/../../../../../webpack/bootstrap 069b89435249357eaca3","webpack://[name]/../../../../../src/app/stats.js","webpack://[name]/../../../../../src/app/gwas-credible-sets.js","webpack://[name]/../../../../../src/app/scoring.js","webpack://[name]/../../../../../src/app/marking.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-credible-sets.js"],"names":["factory","module","Object","prototype","hasOwnProperty","call","object","property","ninv","p","a","b","c","d","e","f","q","r","x","Math","abs","sqrt","log","rollup","scoring","stats","marking","_nlogp_to_z2","nlogp","pow","bayesFactors","nlogpvals","cap","Array","isArray","length","z2_2","map","item","capValue","max","exp","normalizeProbabilities","scores","sumValues","reduce","findCredibleSet","probs","cutoff","Number","isNaN","statsTotal","sortedStatsMap","index","sort","runningTotal","result","fill","i","value","score","markBoolean","credibleSetMembers","rescaleCredibleSet","sumMarkers","exports","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","definition","key","o","defineProperty","enumerable","get","obj","prop","install","LocusZoom","BaseUMAdapter","Adapters","add","config","super","arguments","this","_config","assign","threshold","significance_threshold","_prefix_namespace","state","credible_set_threshold","chr","start","end","join","options","assoc_data","base","_buildRequestOptions","_assoc_data","Promise","resolve","assoc_logp_name","_findPrefixedKey","some","val","posteriorProbabilities","credibleSet","credSetScaled","credSetBool","_provider_name","console","error","association_credible_set_tooltip","l","Layouts","html","closable","show","or","hide","and","association_credible_set_layer","id","namespace","data_operations","type","from","name","requires","params","fill_opacity","tooltip","match","send","receive","color","unshift","field","scale_function","parameters","field_value","then","annotation_credible_set_layer","id_field","x_axis","filters","operator","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip_positioning","annotation_credible_set","title","text","style","min_height","height","margin","top","right","bottom","left","inner_border","toolbar","axes","extent","render","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","data_layers","association_credible_set_panel","widgets","push","position","button_html","button_title","layer_name","default_config_display_name","display_name","display","point_shape","point_size","else","legend","shape","size","label","class","breaks","values","association_credible_set_plot","width","responsive_resize","min_region_scale","max_region_scale","panels","use"],"mappings":"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","file":"ext/lz-credible-sets.min.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"gwasCredibleSets\"] = factory();\n\telse\n\t\troot[\"gwasCredibleSets\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 069b89435249357eaca3","/** \n * @module stats \n * @license MIT\n * */\n\n/**\n * The inverse of the standard normal CDF. May be used to determine the Z-score for the desired quantile.\n *\n * This is an implementation of algorithm AS241:\n * https://www.jstor.org/stable/2347330\n * \n * @param {number} p The desired quantile of the standard normal distribution.\n * @returns {number}\n */\nfunction ninv(p) {\n const SPLIT1 = 0.425;\n const SPLIT2 = 5.0;\n const CONST1 = 0.180625;\n const CONST2 = 1.6;\n const a = [\n 3.3871328727963666080E0,\n 1.3314166789178437745E2,\n 1.9715909503065514427E3,\n 1.3731693765509461125E4,\n 4.5921953931549871457E4,\n 6.7265770927008700853E4,\n 3.3430575583588128105E4,\n 2.5090809287301226727E3\n ];\n\n const b = [\n 4.2313330701600911252E1,\n 6.8718700749205790830E2,\n 5.3941960214247511077E3,\n 2.1213794301586595867E4,\n 3.9307895800092710610E4,\n 2.8729085735721942674E4,\n 5.2264952788528545610E3\n ];\n\n const c = [\n 1.42343711074968357734E0,\n 4.63033784615654529590E0,\n 5.76949722146069140550E0,\n 3.64784832476320460504E0,\n 1.27045825245236838258E0,\n 2.41780725177450611770E-1,\n 2.27238449892691845833E-2,\n 7.74545014278341407640E-4\n ];\n\n const d = [\n 2.05319162663775882187E0,\n 1.67638483018380384940E0,\n 6.89767334985100004550E-1,\n 1.48103976427480074590E-1,\n 1.51986665636164571966E-2,\n 5.47593808499534494600E-4,\n 1.05075007164441684324E-9\n ];\n\n const e = [\n 6.65790464350110377720E0,\n 5.46378491116411436990E0,\n 1.78482653991729133580E0,\n 2.96560571828504891230E-1,\n 2.65321895265761230930E-2,\n 1.24266094738807843860E-3,\n 2.71155556874348757815E-5,\n 2.01033439929228813265E-7\n ];\n\n const f = [\n 5.99832206555887937690E-1,\n 1.36929880922735805310E-1,\n 1.48753612908506148525E-2,\n 7.86869131145613259100E-4,\n 1.84631831751005468180E-5,\n 1.42151175831644588870E-7,\n 2.04426310338993978564E-15\n ];\n\n const q = p - 0.5;\n let r, x;\n\n if (Math.abs(q) < SPLIT1) {\n r = CONST1 - q * q;\n return q * ((((((( a[7] * r + a[6] ) * r + a[5] ) * r + a[4] ) * r\n + a[3] ) * r + a[2] ) * r + a[1] ) * r + a[0] ) /\n ((((((( b[6] * r + b[5] ) * r + b[4] ) * r + b[3] ) * r\n + b[2] ) * r + b[1] ) * r + b[0] ) * r + 1.0 );\n }\n else {\n if (q < 0) {\n r = p\n }\n else {\n r = 1.0 - p\n }\n\n if (r > 0) {\n r = Math.sqrt(-Math.log(r));\n if (r <= SPLIT2) {\n r -= CONST2;\n x = ((((((( c[7] * r + c[6] ) * r + c[5] ) * r + c[4] ) * r\n + c[3] ) * r + c[2] ) * r + c[1] ) * r + c[0] ) /\n ((((((( d[6] * r + d[5] ) * r + d[4] ) * r + d[3] ) * r\n + d[2] ) * r + d[1] ) * r + d[0] ) * r + 1.0 );\n }\n else {\n r -= SPLIT2;\n x = ((((((( e[7] * r + e[6] ) * r + e[5] ) * r + e[4] ) * r\n + e[3] ) * r + e[2] ) * r + e[1] ) * r + e[0] ) /\n ((((((( f[6] * r + f[5] ) * r + f[4] ) * r + f[3] ) * r\n + f[2] ) * r + f[1] ) * r + f[0] ) * r + 1.0 );\n }\n }\n else {\n throw('Not implemented')\n }\n\n if (q < 0) {\n x = -x\n }\n\n return x;\n }\n}\n\n// Hack: A single global object representing the contents of the module\nconst rollup = { ninv };\n\nexport { ninv };\nexport default rollup;\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/stats.js","/** \n * Functions for calculating credible sets and Bayes factors from\n * genome-wide association study (GWAS) results. \n * @module gwas-credible-sets \n * @license MIT\n */\n\nimport stats from './stats';\nimport scoring from './scoring';\nimport marking from './marking';\n\n// HACK: Because a primary audience is targets that do not have any module system, we will expose submodules from the\n// top-level module. (by representing each sub-module as a \"rollup object\" that exposes its internal methods)\n// Then, submodules may be accessed as `window.gwasCredibleSets.stats`, etc\n\n// If you are using a real module system, please import from sub-modules directly- these global helpers are a bit of\n// a hack and may go away in the future\nexport { scoring, stats, marking };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/gwas-credible-sets.js","/** \n * @module scoring \n * @license MIT\n */\n\nimport { ninv } from './stats';\n\n\n/**\n * Convert a -log10 p-value to Z^2.\n *\n * Very large -log10 p-values (very small p-values) cannot be converted to a Z-statistic directly in the browser due to\n * limitations in javascript (64-bit floats.) These values are handled using an approximation:\n * for small p-values, Z_i^2 has a linear relationship with -log10 p-value.\n *\n * The approximation begins for -log10 p-values >= 300.\n *\n * @param nlogp\n * @return {number}\n * @private\n */\nfunction _nlogp_to_z2(nlogp) {\n const p = Math.pow(10, -nlogp);\n if (nlogp < 300) {\n // Use exact method when within the range of 64-bit floats (approx 10^-323)\n return Math.pow(ninv(p / 2), 2);\n }\n else {\n // For very small p-values, -log10(pval) and Z^2 have a linear relationship\n // This avoids issues with needing higher precision floats when doing the calculation\n // with ninv\n return (4.59884133027944 * nlogp) - 5.88085867031722\n }\n}\n\n/**\n * Calculate a Bayes factor exp(Z^2 / 2) based on p-values. If the Z-score is very large, the Bayes factors\n * are calculated in an inexact (capped) manner that makes the calculation tractable but preserves comparisons.\n * @param {Number[]} nlogpvals An array of -log10(p-value) entries\n * @param {Boolean} [cap=true] Whether to apply an inexact method. If false, some values in the return array may\n * be represented as \"Infinity\", but the Bayes factors will be directly calculated wherever possible.\n * @return {Number[]} An array of exp(Z^2 / 2) statistics\n */\nfunction bayesFactors(nlogpvals, cap=true) {\n if (!Array.isArray(nlogpvals) || ! nlogpvals.length) {\n throw 'Must provide a non-empty array of pvalues';\n }\n\n // 1. Convert the pvalues to Z^2 / 2 values. Divide by 2 before applying the cap, because it means fewer values will\n // need to be truncated. This does affect some of the raw bayes factors that are returned (when a cap is needed),\n // but the resulting credible set contents / posterior probabilities are unchanged.\n let z2_2 = nlogpvals.map(item => _nlogp_to_z2(item) / 2);\n\n // 2. Calculate bayes factor, using a truncation approach that prevents overrunning the max float64 value\n // (when Z^2 / 2 > 709 or so). As safeguard, we could (but currently don't) check that exp(Z^2 / 2) is not larger\n // than infinity.\n if (cap) {\n const capValue = Math.max(...z2_2) - 708; // The real cap is ~709; this should prevent any value from exceeding it\n if (capValue > 0) {\n z2_2 = z2_2.map(item => (item - capValue));\n }\n }\n return z2_2.map(item => Math.exp(item));\n}\n\n/**\n * Normalize so that sum of all elements = 1.0. This method must be applied to bayes factors before calculating any\n * credible set.\n *\n * @param {Number[]} scores An array of probability scores for all elements in the range\n * @returns {Number[]} Posterior probabilities\n */\nfunction normalizeProbabilities(scores) {\n const sumValues = scores.reduce((a, b) => a + b, 0);\n return scores.map(item => item / sumValues);\n}\n\nconst rollup = { bayesFactors, normalizeProbabilities };\nexport default rollup;\nexport { bayesFactors, normalizeProbabilities };\n\n// Export additional symbols for unit testing only (not part of public interface for the module)\nexport { _nlogp_to_z2 };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/scoring.js","/**\n * @module marking\n * @license MIT\n */\n\n/**\n * Given an array of probabilities, determine which elements of the array fall within the X% credible set,\n * where X is the cutoff value.\n *\n * @param {Number[]} probs Calculated probabilities used to rank the credible set. This method will normalize the\n * provided input to ensure that the values sum to 1.0.\n * @param {Number} [cutoff=0.95] Keep taking items until we have accounted for >= this fraction of the total probability.\n * For example, 0.95 would represent the 95% credible set.\n * @return {Number[]} An array with posterior probabilities (for the items in the credible set), and zero for all\n * other elements. This array is the same length as the provided probabilities array.\n */\nfunction findCredibleSet(probs, cutoff=0.95) {\n // Type checking\n if (!Array.isArray(probs) || !probs.length) {\n throw 'Probs must be a non-empty array';\n }\n if (!(typeof cutoff === 'number' ) || cutoff < 0 || cutoff > 1.0 || Number.isNaN(cutoff)) {\n throw 'Cutoff must be a number between 0 and 1';\n }\n\n const statsTotal = probs.reduce((a, b) => a + b, 0);\n if (statsTotal <= 0) {\n throw 'Sum of provided probabilities must be > 0';\n }\n\n // Sort the probabilities by largest first, while preserving a map to original item order\n const sortedStatsMap = probs\n .map((item, index) => [item, index])\n .sort((a, b) => (b[0] - a[0]));\n\n let runningTotal = 0;\n const result = new Array(sortedStatsMap.length).fill(0);\n for (let i = 0; i < sortedStatsMap.length; i++) {\n let [value, index] = sortedStatsMap[i];\n if (runningTotal < cutoff) {\n // Convert from a raw score to posterior probability by dividing the item under consideration\n // by sum of all probabilities.\n const score = value / statsTotal;\n result[index] = score;\n runningTotal += score;\n } else {\n break;\n }\n }\n return result;\n}\n\n/**\n * Given a numeric [pre-calculated credible set]{@link #findCredibleSet}, return an array of booleans where true\n * denotes membership in the credible set.\n *\n * This is a helper method used when visualizing the members of the credible set by raw membership.\n *\n * @param {Number[]} credibleSetMembers An array indicating contributions to the credible set, where non-members are\n * represented by some falsy value.\n * @return {Boolean[]} An array of booleans identifying whether or not each item is in the credible set.\n * This array is the same length as the provided credible set array.\n */\nfunction markBoolean(credibleSetMembers) {\n return credibleSetMembers.map(item => !!item);\n}\n\n/**\n * Visualization helper method for rescaling data to a predictable output range, eg when range for a color gradient\n * must be specified in advance.\n *\n * Given an array of probabilities for items in a credible set, rescale the probabilities within only the credible\n * set to their total sum.\n *\n * Example for 95% credible set: [0.92, 0.06, 0.02] -> [0.938, 0.061, 0]. The first two elements here\n * belong to the credible set, the last element does not.\n *\n * @param {Number[]} credibleSetMembers Calculated probabilities used to rank the credible set.\n * @return {Number[]} The fraction of credible set probabilities each item accounts for.\n * This array is the same length as the provided credible set.\n */\nfunction rescaleCredibleSet(credibleSetMembers) {\n const sumMarkers = credibleSetMembers.reduce((a, b) => a + b, 0);\n return credibleSetMembers.map(item => item / sumMarkers);\n}\n\nconst rollup = { findCredibleSet, markBoolean, rescaleCredibleSet };\nexport default rollup;\nexport { findCredibleSet, markBoolean, rescaleCredibleSet };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/marking.js","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Custom code used to power credible sets demonstration example. This is not part of the core LocusZoom library,\n * but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~CredibleSetLZ}\n * * {@link module:LocusZoom_Layouts~association_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_layer}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set_plot}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import credibleSets from 'locuszoom/esm/ext/lz-credible-sets';\n * LocusZoom.use(credibleSets);\n * ```\n @module\n*/\n\nimport {marking, scoring} from 'gwas-credible-sets';\n\nfunction install (LocusZoom) {\n const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter');\n\n /**\n * (**extension**) Custom data adapter that calculates the 95% credible set based on provided association data.\n * This source must be requested as the second step in a chain, after a previous step that returns fields required\n * for the calculation. (usually, it follows a request for GWAS summary statistics)\n * @alias module:LocusZoom_Adapters~CredibleSetLZ\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n class CredibleSetLZ extends BaseUMAdapter {\n /**\n * @param {Number} [config.params.threshold=0.95] The credible set threshold (eg 95%). Will continue selecting SNPs\n * until the posterior probabilities add up to at least this fraction of the total.\n * @param {Number} [config.params.significance_threshold=7.301] Do not perform a credible set calculation for this\n * region unless AT LEAST ONE SNP (as -log10p) exceeds the line of GWAS signficance. Otherwise we are declaring a\n * credible set when there is no evidence of anything being significant at all. If one snp is significant, it will\n * create a credible set for the entire region; the resulting set may include things below the line of significance.\n */\n constructor(config) {\n super(...arguments);\n // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p)\n this._config = Object.assign(\n { threshold: 0.95, significance_threshold: 7.301 },\n this._config\n );\n this._prefix_namespace = false;\n }\n\n _getCacheKey (state) {\n const threshold = state.credible_set_threshold || this._config.threshold;\n return [threshold, state.chr, state.start, state.end].join('_');\n }\n\n _buildRequestOptions(options, assoc_data) {\n const base = super._buildRequestOptions(...arguments);\n base._assoc_data = assoc_data;\n return base;\n }\n\n _performRequest(options) {\n const {_assoc_data} = options;\n if (!_assoc_data.length) {\n // No credible set can be calculated because there is no association data for this region\n return Promise.resolve([]);\n }\n\n const assoc_logp_name = this._findPrefixedKey(_assoc_data[0], 'log_pvalue');\n\n const threshold = this._config.threshold;\n\n // Calculate raw bayes factors and posterior probabilities based on information returned from the API\n const nlogpvals = _assoc_data.map((item) => item[assoc_logp_name]);\n\n if (!nlogpvals.some((val) => val >= this._config.significance_threshold)) {\n // If NO points have evidence of significance, define the credible set to be empty\n // (rather than make a credible set that we don't think is meaningful)\n return Promise.resolve(_assoc_data);\n }\n\n try {\n const scores = scoring.bayesFactors(nlogpvals);\n const posteriorProbabilities = scoring.normalizeProbabilities(scores);\n\n // Use scores to mark the credible set in various ways (depending on your visualization preferences,\n // some of these may not be needed)\n const credibleSet = marking.findCredibleSet(posteriorProbabilities, threshold);\n const credSetScaled = marking.rescaleCredibleSet(credibleSet);\n const credSetBool = marking.markBoolean(credibleSet);\n\n // Annotate each response record based on credible set membership. This has the effect of joining\n // credset results to assoc data directly within the adapter (no separate join needed)\n for (let i = 0; i < _assoc_data.length; i++) {\n _assoc_data[i][`${options._provider_name}:posterior_prob`] = posteriorProbabilities[i];\n _assoc_data[i][`${options._provider_name}:contrib_fraction`] = credSetScaled[i];\n _assoc_data[i][`${options._provider_name}:is_member`] = credSetBool[i];\n }\n } catch (e) {\n // If the calculation cannot be completed, return the data without annotation fields\n console.error(e);\n }\n return Promise.resolve(_assoc_data);\n }\n }\n\n LocusZoom.Adapters.add('CredibleSetLZ', CredibleSetLZ);\n\n // Add related layouts to the central global registry\n /**\n * (**extension**) Tooltip layout that appends credible set posterior probability to the default association tooltip (for SNPs in the credible set)\n * @alias module:LocusZoom_Layouts~association_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_tooltip = function () {\n // Extend a known tooltip with an extra row of info showing posterior probabilities\n const l = LocusZoom.Layouts.get('tooltip', 'standard_association');\n l.html += '{{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}';\n return l;\n }();\n\n LocusZoom.Layouts.add('tooltip', 'association_credible_set', association_credible_set_tooltip);\n\n /**\n * (**extension**) A tooltip layout for annotation (rug) tracks that provides information about credible set members\n * @alias module:LocusZoom_Layouts~annotation_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{assoc:variant|htmlescape}}
    '\n + 'P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    ' +\n '{{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}',\n };\n LocusZoom.Layouts.add('tooltip', 'annotation_credible_set', annotation_credible_set_tooltip);\n\n /**\n * (**extension**) A data layer layout that shows GWAS summary statistics overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n\n const association_credible_set_layer = function () {\n const base = LocusZoom.Layouts.get('data_layer', 'association_pvalues', {\n id: 'associationcredibleset',\n namespace: { 'assoc': 'assoc', 'credset': 'credset', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)', 'credset(assoc)'],\n },\n {\n type: 'left_match',\n name: 'credset_plus_ld',\n requires: ['credset', 'ld'], // The credible sets demo wasn't fully moved over to the new data operations system, and as such it is a bit weird\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n fill_opacity: 0.7,\n tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set'),\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n });\n base.color.unshift({\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#FFf000',\n },\n });\n return base;\n }();\n LocusZoom.Layouts.add('data_layer', 'association_credible_set', association_credible_set_layer);\n\n /**\n * (**extension**) A data layer layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_layer = {\n namespace: { 'assoc': 'assoc', 'credset': 'credset' },\n data_operations: [{\n type: 'fetch',\n from: ['assoc', 'credset(assoc)'],\n }],\n id: 'annotationcredibleset',\n type: 'annotation_track',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#001cee',\n },\n },\n '#00CC00',\n ],\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'credset:is_member', operator: '=', value: true },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: LocusZoom.Layouts.get('tooltip', 'annotation_credible_set'),\n tooltip_positioning: 'top',\n };\n LocusZoom.Layouts.add('data_layer', 'annotation_credible_set', annotation_credible_set_layer);\n\n /**\n * (**extension**) A panel layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set = {\n id: 'annotationcredibleset',\n title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } },\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 50, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel'),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'annotation_credible_set'),\n ],\n };\n LocusZoom.Layouts.add('panel', 'annotation_credible_set', annotation_credible_set);\n\n /**\n * (**extension**) A panel layout that shows GWAS summary statistics in a standard LocusZoom view, overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_panel = function () {\n const l = LocusZoom.Layouts.get('panel', 'association', {\n id: 'associationcrediblesets',\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'significance'),\n LocusZoom.Layouts.get('data_layer', 'recomb_rate'),\n LocusZoom.Layouts.get('data_layer', 'association_credible_set'),\n ],\n });\n // Add \"display options\" button to control how credible set coloring is overlaid on the standard association plot\n l.toolbar.widgets.push(\n {\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n layer_name: 'associationcredibleset',\n default_config_display_name: 'Linkage Disequilibrium (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: '95% credible set (boolean)', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n point_shape: 'circle',\n point_size: 40,\n color: {\n field: 'credset:is_member',\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#00CC00',\n else: '#CCCCCC',\n },\n },\n legend: [ // Tells the legend how to represent this display option\n {\n shape: 'circle',\n color: '#00CC00',\n size: 40,\n label: 'In credible set',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#CCCCCC',\n size: 40,\n label: 'Not in credible set',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n {\n // Second option. The same plot- or even the same field- can be colored in more than one way.\n display_name: '95% credible set (gradient by contribution)',\n display: {\n point_shape: 'circle',\n point_size: 40,\n color: [\n {\n field: 'credset:contrib_fraction',\n scale_function: 'if',\n parameters: {\n field_value: 0,\n then: '#777777',\n },\n },\n {\n scale_function: 'interpolate',\n field: 'credset:contrib_fraction',\n parameters: {\n breaks: [0, 1],\n values: ['#fafe87', '#9c0000'],\n },\n },\n ],\n legend: [\n {\n shape: 'circle',\n color: '#777777',\n size: 40,\n label: 'No contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#fafe87',\n size: 40,\n label: 'Some contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#9c0000',\n size: 40,\n label: 'Most contribution',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n ],\n }\n );\n return l;\n }();\n LocusZoom.Layouts.add('panel', 'association_credible_set', association_credible_set_panel);\n\n /**\n * (**extension**) A standard LocusZoom plot layout, with additional credible set information.\n * @alias module:LocusZoom_Layouts~association_credible_set_plot\n * @type plot\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_plot = {\n state: {},\n width: 800,\n height: 450,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n LocusZoom.Layouts.get('panel', 'association_credible_set'),\n LocusZoom.Layouts.get('panel', 'annotation_credible_set'),\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n LocusZoom.Layouts.add('plot', 'association_credible_set', association_credible_set_plot);\n}\n\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-dynamic-urls.min.js b/dist/ext/lz-dynamic-urls.min.js index 543361e4..0af428b9 100644 --- a/dist/ext/lz-dynamic-urls.min.js +++ b/dist/ext/lz-dynamic-urls.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.1 */ +/*! Locuszoom 0.14.0-beta.2 */ var LzDynamicUrls;(()=>{"use strict";var t={d:(n,e)=>{for(var o in e)t.o(e,o)&&!t.o(n,o)&&Object.defineProperty(n,o,{enumerable:!0,get:e[o]})},o:(t,n)=>Object.prototype.hasOwnProperty.call(t,n)},n={};function e(t){const n={};if(t){const e=("?"===t[0]?t.substr(1):t).split("&");for(let t=0;ta});const a={paramsFromUrl:s,extractValues:o,plotUpdatesUrl:function(t,n,o){o=o||r;const c=function(c){const r=e(window.location.search),s=o(t,n,c),a=Object.assign({},r,s);if(Object.keys(a).some((function(t){return r[t]!=a[t]}))){const t=(i=a,`?${Object.keys(i).map((function(t){return`${encodeURIComponent(t)}=${encodeURIComponent(i[t])}`})).join("&")}`);Object.keys(r).length?history.pushState({},document.title,t):history.replaceState({},document.title,t)}var i};return t.on("state_changed",c),c},plotWatchesUrl:function(t,n,e){e=e||c;const o=function(o){const c=s(n);e(t,c)};return window.addEventListener("popstate",o),t.trackExternalListener(window,"popstate",o),o}};LzDynamicUrls=n.default})(); //# sourceMappingURL=lz-dynamic-urls.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-forest-track.min.js b/dist/ext/lz-forest-track.min.js index a140e611..a1535119 100644 --- a/dist/ext/lz-forest-track.min.js +++ b/dist/ext/lz-forest-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.1 */ +/*! Locuszoom 0.14.0-beta.2 */ var LzForestTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var i in a)t.o(a,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:a[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>s});const a=d3;function i(t){const e=t.DataLayers.get("BaseDataLayer"),i={point_size:40,point_shape:"square",color:"#888888",fill_opacity:1,y_axis:{axis:2},id_field:"id",confidence_intervals:{start_field:"ci_start",end_field:"ci_end"}};class s extends e{constructor(e){e=t.Layouts.merge(e,i),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),a=`y${this.layout.y_axis.axis}_scale`,i=this.parent[a](t.data[this.layout.y_axis.field]),s=this.resolveScalableParameter(this.layout.point_size,t.data),r=Math.sqrt(s/Math.PI);return{x_min:e-r,x_max:e+r,y_min:i-r,y_max:i+r}}render(){const t=this._applyFilters(),e=`y${this.layout.y_axis.axis}_scale`;if(this.layout.confidence_intervals&&this.layout.confidence_intervals.start_field&&this.layout.confidence_intervals.end_field){const a=this.svg.group.selectAll("rect.lz-data_layer-forest.lz-data_layer-forest-ci").data(t,(t=>t[this.layout.id_field])),i=t=>{let a=this.parent.x_scale(t[this.layout.confidence_intervals.start_field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`},s=t=>{const{start_field:e,end_field:a}=this.layout.confidence_intervals,i=this.parent.x_scale,s=i(t[a])-i(t[e]);return Math.max(s,1)},r=1;a.enter().append("rect").attr("class","lz-data_layer-forest lz-data_layer-forest-ci").attr("id",(t=>`${this.getElementId(t)}_ci`)).attr("transform",`translate(0, ${isNaN(this.parent.layout.height)?0:this.parent.layout.height})`).merge(a).attr("transform",i).attr("width",s).attr("height",r),a.exit().remove()}const i=this.svg.group.selectAll("path.lz-data_layer-forest.lz-data_layer-forest-point").data(t,(t=>t[this.layout.id_field])),s=isNaN(this.parent.layout.height)?0:this.parent.layout.height,r=a.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>{const i=this.resolveScalableParameter(this.layout.point_shape,t,e),s=`symbol${i.charAt(0).toUpperCase()+i.slice(1)}`;return a[s]||null}));i.enter().append("path").attr("class","lz-data_layer-forest lz-data_layer-forest-point").attr("id",(t=>this.getElementId(t))).attr("transform",`translate(0, ${s})`).merge(i).attr("transform",(t=>{let a=this.parent.x_scale(t[this.layout.x_axis.field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`})).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>{this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}}t.DataLayers.add("forest",s),t.DataLayers.add("category_forest",class extends s{_getDataExtent(t,e){const{confidence_intervals:i}=this.layout;if(i&&i.start_field&&i.end_field){const e=t=>+t[i.start_field],s=t=>+t[i.end_field];return[a.min(t,e),a.max(t,s)]}return super._getDataExtent(t,e)}getTicks(t,e){if(!["x","y1","y2"].includes(t))throw new Error(`Invalid dimension identifier ${t}`);if(t===`y${this.layout.y_axis.axis}`){const t=this.layout.y_axis.category_field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify category_field`);return this.data.map(((e,a)=>({y:a+1,text:e[t]})))}return[]}applyCustomDataMethods(){const t=this.layout.y_axis.field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify yaxis.field`);return this.data=this.data.map(((e,a)=>(e[t]=a+1,e))),this.layout.y_axis.floor=0,this.layout.y_axis.ceiling=this.data.length+1,this}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(i);const s=i;LzForestTrack=e.default})(); //# sourceMappingURL=lz-forest-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-enrichment.min.js b/dist/ext/lz-intervals-enrichment.min.js index f854c445..796ea5fe 100644 --- a/dist/ext/lz-intervals-enrichment.min.js +++ b/dist/ext/lz-intervals-enrichment.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.1 */ -var LzIntervalsEnrichment;(()=>{"use strict";var e={d:(t,a)=>{for(var i in a)e.o(a,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:a[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>n});const a=Symbol.for("lzXCS"),i=Symbol.for("lzYCS"),s=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function r(e){const t={start_field:"start",end_field:"end",track_height:10,track_vertical_spacing:3,bounding_box_padding:2,color:"#B8B8B8",fill_opacity:.5,tooltip_positioning:"vertical"},r=e.DataLayers.get("BaseDataLayer");const n={namespace:{intervals:"intervals"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Tissue: {{intervals:tissueId|htmlescape}}
    \n Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
    \n -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
    \n Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}"},o={id:"intervals_enrichment",type:"intervals_enrichment",tag:"intervals_enrichment",namespace:{intervals:"intervals"},match:{send:"intervals:tissueId"},id_field:"intervals:start",start_field:"intervals:start",end_field:"intervals:end",filters:[{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],y_axis:{axis:1,field:"intervals:fold",floor:0,upper_buffer:.1,min_extent:[0,10]},fill_opacity:.5,color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:n},c={id:"interval_matches",type:"highlight_regions",namespace:{intervals:"intervals"},match:{receive:"intervals:tissueId"},start_field:"intervals:start",end_field:"intervals:end",merge_field:"intervals:tissueId",filters:[{field:"lz_is_match",operator:"=",value:!0},{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],fill_opacity:.1},d={id:"intervals_enrichment",tag:"intervals_enrichment",min_height:250,height:250,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"},y1:{label:"enrichment (n-fold)",label_offset:28}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[o]},h={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:e.Layouts.get("toolbar","standard_association"),panels:[function(){const t=e.Layouts.get("panel","association");return t.data_layers.unshift(c),t}(),d,e.Layouts.get("panel","genes")]};e.DataLayers.add("intervals_enrichment",class extends r{constructor(a){e.Layouts.merge(a,t),super(...arguments)}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}render(){const e=this._applyFilters(this.data),{start_field:t,end_field:r,bounding_box_padding:n,track_height:o}=this.layout,c=this.layout.y_axis.field,d=`y${this.layout.y_axis.axis}_scale`,{x_scale:h,[d]:f}=this.parent;e.forEach((e=>{e[a]=h(e[t]),e[s]=h(e[r]),e[i]=f(e[c])-this.getTrackHeight()/2+n,e[l]=e[i]+o})),e.sort(((e,t)=>{const i=e[s]-e[a];return t[s]-t[a]-i}));const _=this.svg.group.selectAll("rect").data(e);_.enter().append("rect").merge(_).attr("id",(e=>this.getElementId(e))).attr("x",(e=>e[a])).attr("y",(e=>e[i])).attr("width",(e=>e[s]-e[a])).attr("height",this.layout.track_height).attr("fill",((e,t)=>this.resolveScalableParameter(this.layout.color,e,t))).attr("fill-opacity",((e,t)=>this.resolveScalableParameter(this.layout.fill_opacity,e,t))),_.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this))}_getTooltipPosition(e){return{x_min:e.data[a],x_max:e.data[s],y_min:e.data[i],y_max:e.data[l]}}}),e.Layouts.add("tooltip","intervals_enrichment",n),e.Layouts.add("data_layer","intervals_enrichment",o),e.Layouts.add("panel","intervals_enrichment",d),e.Layouts.add("plot","intervals_association_enrichment",h)}"undefined"!=typeof LocusZoom&&LocusZoom.use(r);const n=r;LzIntervalsEnrichment=t.default})(); +/*! Locuszoom 0.14.0-beta.2 */ +var LzIntervalsEnrichment;(()=>{"use strict";var e={d:(t,a)=>{for(var i in a)e.o(a,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:a[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>n});const a=Symbol.for("lzXCS"),i=Symbol.for("lzYCS"),s=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function r(e){const t={start_field:"start",end_field:"end",track_height:10,track_vertical_spacing:3,bounding_box_padding:2,color:"#B8B8B8",fill_opacity:.5,tooltip_positioning:"vertical"},r=e.DataLayers.get("BaseDataLayer");const n={namespace:{intervals:"intervals"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Tissue: {{intervals:tissueId|htmlescape}}
    \n Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
    \n -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
    \n Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}"},o={id:"intervals_enrichment",type:"intervals_enrichment",tag:"intervals_enrichment",namespace:{intervals:"intervals"},match:{send:"intervals:tissueId"},id_field:"intervals:start",start_field:"intervals:start",end_field:"intervals:end",filters:[{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],y_axis:{axis:1,field:"intervals:fold",floor:0,upper_buffer:.1,min_extent:[0,10]},fill_opacity:.5,color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:n},c={id:"interval_matches",type:"highlight_regions",namespace:{intervals:"intervals"},match:{receive:"intervals:tissueId"},start_field:"intervals:start",end_field:"intervals:end",merge_field:"intervals:tissueId",filters:[{field:"lz_is_match",operator:"=",value:!0},{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],fill_opacity:.1},d={id:"intervals_enrichment",tag:"intervals_enrichment",min_height:250,height:250,margin:{top:35,right:50,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:34,tick_format:"region",extent:"state"},y1:{label:"enrichment (n-fold)",label_offset:40}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[o]},h={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:e.Layouts.get("toolbar","standard_association"),panels:[function(){const t=e.Layouts.get("panel","association");return t.data_layers.unshift(c),t}(),d,e.Layouts.get("panel","genes")]};e.DataLayers.add("intervals_enrichment",class extends r{constructor(a){e.Layouts.merge(a,t),super(...arguments)}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}render(){const e=this._applyFilters(this.data),{start_field:t,end_field:r,bounding_box_padding:n,track_height:o}=this.layout,c=this.layout.y_axis.field,d=`y${this.layout.y_axis.axis}_scale`,{x_scale:h,[d]:f}=this.parent;e.forEach((e=>{e[a]=h(e[t]),e[s]=h(e[r]),e[i]=f(e[c])-this.getTrackHeight()/2+n,e[l]=e[i]+o})),e.sort(((e,t)=>{const i=e[s]-e[a];return t[s]-t[a]-i}));const _=this.svg.group.selectAll("rect").data(e);_.enter().append("rect").merge(_).attr("id",(e=>this.getElementId(e))).attr("x",(e=>e[a])).attr("y",(e=>e[i])).attr("width",(e=>e[s]-e[a])).attr("height",this.layout.track_height).attr("fill",((e,t)=>this.resolveScalableParameter(this.layout.color,e,t))).attr("fill-opacity",((e,t)=>this.resolveScalableParameter(this.layout.fill_opacity,e,t))),_.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this))}_getTooltipPosition(e){return{x_min:e.data[a],x_max:e.data[s],y_min:e.data[i],y_max:e.data[l]}}}),e.Layouts.add("tooltip","intervals_enrichment",n),e.Layouts.add("data_layer","intervals_enrichment",o),e.Layouts.add("panel","intervals_enrichment",d),e.Layouts.add("plot","intervals_association_enrichment",h)}"undefined"!=typeof LocusZoom&&LocusZoom.use(r);const n=r;LzIntervalsEnrichment=t.default})(); //# sourceMappingURL=lz-intervals-enrichment.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-enrichment.min.js.map b/dist/ext/lz-intervals-enrichment.min.js.map index 1efdc478..29de6128 100644 --- a/dist/ext/lz-intervals-enrichment.min.js.map +++ b/dist/ext/lz-intervals-enrichment.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-intervals-enrichment.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","XCS","Symbol","for","YCS","XCE","YCE","install","LocusZoom","default_layout","start_field","end_field","track_height","track_vertical_spacing","bounding_box_padding","color","fill_opacity","tooltip_positioning","BaseLayer","DataLayers","intervals_tooltip_layout","namespace","closable","show","or","hide","and","html","intervals_layer_layout","id","type","tag","match","send","id_field","filters","field","operator","value","y_axis","axis","floor","upper_buffer","min_extent","scale_function","parameters","values","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip","intervals_highlight_layout","intervals","receive","merge_field","intervals_panel_layout","min_height","height","margin","top","right","bottom","left","inner_border","axes","x","label","label_offset","tick_format","extent","y1","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","scroll_to_zoom","x_linked","data_layers","intervals_plot_layout","state","width","responsive_resize","min_region_scale","max_region_scale","toolbar","Layouts","panels","base","unshift","add","layout","merge","super","arguments","this","track_data","_applyFilters","data","y_field","y_axis_name","x_scale","y_scale","parent","forEach","item","getTrackHeight","sort","a","b","aspan","selection","svg","group","selectAll","enter","append","attr","d","getElementId","i","resolveScalableParameter","exit","remove","applyBehaviors","bind","x_min","x_max","y_min","y_max","use"],"mappings":"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","file":"ext/lz-intervals-enrichment.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Interval annotation track that groups annotations by enrichment value (a fixed y-axis) rather than by merged/split tracks.\n\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n\n * ### Features provided\n * * {@link module:LocusZoom_DataLayers~intervals_enrichment}\n * * {@link module:LocusZoom_Layouts~intervals_association_enrichment}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_panel}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_data_layer}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_tooltip}\n *\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```javascript\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n *\n * ```javascript\n * import LocusZoom from 'locuszoom';\n * import IntervalsTrack from 'locuszoom/esm/ext/lz-intervals-track';\n * LocusZoom.use(IntervalsTrack);\n * ```\n *\n * Then use the layouts made available by this extension. (see demos and documentation for guidance)\n * @module\n */\n\n// Coordinates (start, end) are cached to facilitate rendering\nconst XCS = Symbol.for('lzXCS');\nconst YCS = Symbol.for('lzYCS');\nconst XCE = Symbol.for('lzXCE');\nconst YCE = Symbol.for('lzYCE');\n\n\nfunction install(LocusZoom) {\n /**\n * @memberof module:LocusZoom_DataLayers~intervals_enrichment\n */\n const default_layout = {\n start_field: 'start',\n end_field: 'end',\n track_height: 10,\n track_vertical_spacing: 3,\n bounding_box_padding: 2,\n color: '#B8B8B8',\n fill_opacity: 0.5,\n tooltip_positioning: 'vertical',\n };\n\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n\n /**\n * Intervals-by-enrichment Data Layer\n *\n * Implements a data layer that groups interval annotations by enrichment value (a fixed y-axis)\n * @alias module:LocusZoom_DataLayers~intervals_enrichment\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\n class LzIntervalsEnrichment extends BaseLayer {\n /**\n * @param {string} [layout.start_field='start'] The field that defines interval start position\n * @param {string} [layout.end_field='end'] The field that defines interval end position\n * @param {number} [layout.track_height=10] The height of each interval rectangle, in px\n * @param {number} [layout.track_vertical_spacing=3]\n * @param {number} [layout.bounding_box_padding=2]\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#B8B8B8'] The color of each datum rectangle\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity of\n * each rectangle. The default is semi-transparent, because low-significance tracks may overlap very closely.\n * @param {string} [layout.tooltip_positioning='vertical']\n */\n constructor(layout) {\n LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n }\n\n // Helper function to sum layout values to derive total height for a single interval track\n getTrackHeight() {\n return this.layout.track_height\n + this.layout.track_vertical_spacing\n + (2 * this.layout.bounding_box_padding);\n }\n\n render() {\n // Determine the appropriate layout for tracks. Store the previous categories (y axis ticks) to decide\n // whether the axis needs to be re-rendered.\n\n // Apply filters to only render a specified set of points. Hidden fields will still be given space to render, but not shown.\n const track_data = this._applyFilters(this.data);\n\n const {start_field, end_field, bounding_box_padding, track_height} = this.layout;\n const y_field = this.layout.y_axis.field;\n const y_axis_name = `y${this.layout.y_axis.axis}_scale`;\n const { x_scale, [y_axis_name]: y_scale } = this.parent;\n\n // Calculate coordinates for each point\n track_data.forEach((item) => {\n item[XCS] = x_scale(item[start_field]);\n item[XCE] = x_scale(item[end_field]);\n item[YCS] = y_scale(item[y_field]) - this.getTrackHeight() / 2 + bounding_box_padding;\n item[YCE] = item[YCS] + track_height;\n });\n\n track_data.sort((a, b) => {\n // Simplistic layout algorithm that adds wide rectangles to the DOM first, so that small rectangles\n // in the same space are clickable (SVG element order determines z-index)\n const aspan = a[XCE] - a[XCS];\n const bspan = b[XCE] - b[XCS];\n return bspan - aspan;\n });\n\n const selection = this.svg.group.selectAll('rect')\n .data(track_data);\n\n selection.enter()\n .append('rect')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => d[XCS])\n .attr('y', (d) => d[YCS])\n .attr('width', (d) => d[XCE] - d[XCS])\n .attr('height', this.layout.track_height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n selection.exit()\n .remove();\n\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n return {\n x_min: tooltip.data[XCS],\n x_max: tooltip.data[XCE],\n y_min: tooltip.data[YCS],\n y_max: tooltip.data[YCE],\n };\n }\n }\n\n /**\n * (**extension**) A basic tooltip with information to be shown over an intervals-by-enrichment datum\n * @alias module:LocusZoom_Layouts~intervals_enrichment_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_tooltip_layout = {\n namespace: { 'intervals': 'intervals' },\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Tissue: {{intervals:tissueId|htmlescape}}
    \n Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
    \n -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
    \n Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}`,\n };\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals-by-enrichment display, in\n * which intervals are ranked by priority from enrichment analysis.\n *\n * @alias module:LocusZoom_Layouts~intervals_enrichment_data_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_layer_layout = {\n id: 'intervals_enrichment',\n type: 'intervals_enrichment',\n tag: 'intervals_enrichment',\n namespace: { 'intervals': 'intervals' },\n match: { send: 'intervals:tissueId' },\n id_field: 'intervals:start', // not a good ID field for overlapping intervals\n start_field: 'intervals:start',\n end_field: 'intervals:end',\n filters: [\n { field: 'intervals:ancestry', operator: '=', value: 'EU' },\n { field: 'intervals:pValue', operator: '<=', value: 0.05 },\n { field: 'intervals:fold', operator: '>', value: 2.0 },\n ],\n y_axis: {\n axis: 1,\n field: 'intervals:fold', // is this used for other than extent generation?\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n fill_opacity: 0.5, // Many intervals overlap: show all, even if the ones below can't be clicked\n color: [\n {\n field: 'intervals:tissueId',\n scale_function: 'stable_choice',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'],\n },\n },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: intervals_tooltip_layout,\n };\n\n // This is tied to a rather specific demo, so it's not added to the reusable registry\n // Highlights areas of a scatter plot that match the HuGeAMP-provided enrichment analysis data\n // Relies on matching behavior/ interaction (not visible initially)\n const intervals_highlight_layout = {\n id: 'interval_matches',\n type: 'highlight_regions',\n namespace: { intervals: 'intervals' },\n match: { receive: 'intervals:tissueId' },\n start_field: 'intervals:start',\n end_field: 'intervals:end',\n merge_field: 'intervals:tissueId',\n filters: [\n { field: 'lz_is_match', operator: '=', value: true },\n { field: 'intervals:ancestry', operator: '=', value: 'EU' },\n { field: 'intervals:pValue', operator: '<=', value: 0.05 },\n { field: 'intervals:fold', operator: '>', value: 2.0 },\n ],\n color: [{\n field: 'intervals:tissueId',\n scale_function: 'stable_choice',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'],\n },\n }],\n fill_opacity: 0.1,\n };\n\n /**\n * (**extension**) A panel containing an intervals-by-enrichment data layer\n * @alias module:LocusZoom_Layouts~intervals_enrichment_panel\n * @type panel\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_panel_layout = {\n id: 'intervals_enrichment',\n tag: 'intervals_enrichment',\n min_height: 250,\n height: 250,\n margin: { top: 35, right: 50, bottom: 40, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'enrichment (n-fold)',\n label_offset: 28,\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [intervals_layer_layout],\n };\n\n /**\n * (**extension**) A plot layout that shows association summary statistics, genes, and intervals-by-enrichment data.\n * This layout provides interactive matching: clicking an interval marking causes area of the scatter plot to be\n * highlighted for any annotations that match the specified category.\n * It is intended to work with data in the HuGeAMP format.\n * @alias module:LocusZoom_Layouts~intervals_association_enrichment\n * @type plot\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_plot_layout = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n function () {\n const base = LocusZoom.Layouts.get('panel', 'association');\n base.data_layers.unshift(intervals_highlight_layout);\n return base;\n }(),\n intervals_panel_layout,\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n\n LocusZoom.DataLayers.add('intervals_enrichment', LzIntervalsEnrichment);\n\n LocusZoom.Layouts.add('tooltip', 'intervals_enrichment', intervals_tooltip_layout);\n LocusZoom.Layouts.add('data_layer', 'intervals_enrichment', intervals_layer_layout);\n LocusZoom.Layouts.add('panel', 'intervals_enrichment', intervals_panel_layout);\n LocusZoom.Layouts.add('plot', 'intervals_association_enrichment', intervals_plot_layout);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-intervals-enrichment.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","XCS","Symbol","for","YCS","XCE","YCE","install","LocusZoom","default_layout","start_field","end_field","track_height","track_vertical_spacing","bounding_box_padding","color","fill_opacity","tooltip_positioning","BaseLayer","DataLayers","intervals_tooltip_layout","namespace","closable","show","or","hide","and","html","intervals_layer_layout","id","type","tag","match","send","id_field","filters","field","operator","value","y_axis","axis","floor","upper_buffer","min_extent","scale_function","parameters","values","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip","intervals_highlight_layout","intervals","receive","merge_field","intervals_panel_layout","min_height","height","margin","top","right","bottom","left","inner_border","axes","x","label","label_offset","tick_format","extent","y1","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","scroll_to_zoom","x_linked","data_layers","intervals_plot_layout","state","width","responsive_resize","min_region_scale","max_region_scale","toolbar","Layouts","panels","base","unshift","add","layout","merge","super","arguments","this","track_data","_applyFilters","data","y_field","y_axis_name","x_scale","y_scale","parent","forEach","item","getTrackHeight","sort","a","b","aspan","selection","svg","group","selectAll","enter","append","attr","d","getElementId","i","resolveScalableParameter","exit","remove","applyBehaviors","bind","x_min","x_max","y_min","y_max","use"],"mappings":"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","file":"ext/lz-intervals-enrichment.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Interval annotation track that groups annotations by enrichment value (a fixed y-axis) rather than by merged/split tracks.\n\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n\n * ### Features provided\n * * {@link module:LocusZoom_DataLayers~intervals_enrichment}\n * * {@link module:LocusZoom_Layouts~intervals_association_enrichment}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_panel}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_data_layer}\n * * {@link module:LocusZoom_Layouts~intervals_enrichment_tooltip}\n *\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```javascript\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n *\n * ```javascript\n * import LocusZoom from 'locuszoom';\n * import IntervalsTrack from 'locuszoom/esm/ext/lz-intervals-track';\n * LocusZoom.use(IntervalsTrack);\n * ```\n *\n * Then use the layouts made available by this extension. (see demos and documentation for guidance)\n * @module\n */\n\n// Coordinates (start, end) are cached to facilitate rendering\nconst XCS = Symbol.for('lzXCS');\nconst YCS = Symbol.for('lzYCS');\nconst XCE = Symbol.for('lzXCE');\nconst YCE = Symbol.for('lzYCE');\n\n\nfunction install(LocusZoom) {\n /**\n * @memberof module:LocusZoom_DataLayers~intervals_enrichment\n */\n const default_layout = {\n start_field: 'start',\n end_field: 'end',\n track_height: 10,\n track_vertical_spacing: 3,\n bounding_box_padding: 2,\n color: '#B8B8B8',\n fill_opacity: 0.5,\n tooltip_positioning: 'vertical',\n };\n\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n\n /**\n * Intervals-by-enrichment Data Layer\n *\n * Implements a data layer that groups interval annotations by enrichment value (a fixed y-axis)\n * @alias module:LocusZoom_DataLayers~intervals_enrichment\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\n class LzIntervalsEnrichment extends BaseLayer {\n /**\n * @param {string} [layout.start_field='start'] The field that defines interval start position\n * @param {string} [layout.end_field='end'] The field that defines interval end position\n * @param {number} [layout.track_height=10] The height of each interval rectangle, in px\n * @param {number} [layout.track_vertical_spacing=3]\n * @param {number} [layout.bounding_box_padding=2]\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#B8B8B8'] The color of each datum rectangle\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity of\n * each rectangle. The default is semi-transparent, because low-significance tracks may overlap very closely.\n * @param {string} [layout.tooltip_positioning='vertical']\n */\n constructor(layout) {\n LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n }\n\n // Helper function to sum layout values to derive total height for a single interval track\n getTrackHeight() {\n return this.layout.track_height\n + this.layout.track_vertical_spacing\n + (2 * this.layout.bounding_box_padding);\n }\n\n render() {\n // Determine the appropriate layout for tracks. Store the previous categories (y axis ticks) to decide\n // whether the axis needs to be re-rendered.\n\n // Apply filters to only render a specified set of points. Hidden fields will still be given space to render, but not shown.\n const track_data = this._applyFilters(this.data);\n\n const {start_field, end_field, bounding_box_padding, track_height} = this.layout;\n const y_field = this.layout.y_axis.field;\n const y_axis_name = `y${this.layout.y_axis.axis}_scale`;\n const { x_scale, [y_axis_name]: y_scale } = this.parent;\n\n // Calculate coordinates for each point\n track_data.forEach((item) => {\n item[XCS] = x_scale(item[start_field]);\n item[XCE] = x_scale(item[end_field]);\n item[YCS] = y_scale(item[y_field]) - this.getTrackHeight() / 2 + bounding_box_padding;\n item[YCE] = item[YCS] + track_height;\n });\n\n track_data.sort((a, b) => {\n // Simplistic layout algorithm that adds wide rectangles to the DOM first, so that small rectangles\n // in the same space are clickable (SVG element order determines z-index)\n const aspan = a[XCE] - a[XCS];\n const bspan = b[XCE] - b[XCS];\n return bspan - aspan;\n });\n\n const selection = this.svg.group.selectAll('rect')\n .data(track_data);\n\n selection.enter()\n .append('rect')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => d[XCS])\n .attr('y', (d) => d[YCS])\n .attr('width', (d) => d[XCE] - d[XCS])\n .attr('height', this.layout.track_height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n selection.exit()\n .remove();\n\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n return {\n x_min: tooltip.data[XCS],\n x_max: tooltip.data[XCE],\n y_min: tooltip.data[YCS],\n y_max: tooltip.data[YCE],\n };\n }\n }\n\n /**\n * (**extension**) A basic tooltip with information to be shown over an intervals-by-enrichment datum\n * @alias module:LocusZoom_Layouts~intervals_enrichment_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_tooltip_layout = {\n namespace: { 'intervals': 'intervals' },\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Tissue: {{intervals:tissueId|htmlescape}}
    \n Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
    \n -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
    \n Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}`,\n };\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals-by-enrichment display, in\n * which intervals are ranked by priority from enrichment analysis.\n *\n * @alias module:LocusZoom_Layouts~intervals_enrichment_data_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_layer_layout = {\n id: 'intervals_enrichment',\n type: 'intervals_enrichment',\n tag: 'intervals_enrichment',\n namespace: { 'intervals': 'intervals' },\n match: { send: 'intervals:tissueId' },\n id_field: 'intervals:start', // not a good ID field for overlapping intervals\n start_field: 'intervals:start',\n end_field: 'intervals:end',\n filters: [\n { field: 'intervals:ancestry', operator: '=', value: 'EU' },\n { field: 'intervals:pValue', operator: '<=', value: 0.05 },\n { field: 'intervals:fold', operator: '>', value: 2.0 },\n ],\n y_axis: {\n axis: 1,\n field: 'intervals:fold', // is this used for other than extent generation?\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n fill_opacity: 0.5, // Many intervals overlap: show all, even if the ones below can't be clicked\n color: [\n {\n field: 'intervals:tissueId',\n scale_function: 'stable_choice',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'],\n },\n },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: intervals_tooltip_layout,\n };\n\n // This is tied to a rather specific demo, so it's not added to the reusable registry\n // Highlights areas of a scatter plot that match the HuGeAMP-provided enrichment analysis data\n // Relies on matching behavior/ interaction (not visible initially)\n const intervals_highlight_layout = {\n id: 'interval_matches',\n type: 'highlight_regions',\n namespace: { intervals: 'intervals' },\n match: { receive: 'intervals:tissueId' },\n start_field: 'intervals:start',\n end_field: 'intervals:end',\n merge_field: 'intervals:tissueId',\n filters: [\n { field: 'lz_is_match', operator: '=', value: true },\n { field: 'intervals:ancestry', operator: '=', value: 'EU' },\n { field: 'intervals:pValue', operator: '<=', value: 0.05 },\n { field: 'intervals:fold', operator: '>', value: 2.0 },\n ],\n color: [{\n field: 'intervals:tissueId',\n scale_function: 'stable_choice',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'],\n },\n }],\n fill_opacity: 0.1,\n };\n\n /**\n * (**extension**) A panel containing an intervals-by-enrichment data layer\n * @alias module:LocusZoom_Layouts~intervals_enrichment_panel\n * @type panel\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_panel_layout = {\n id: 'intervals_enrichment',\n tag: 'intervals_enrichment',\n min_height: 250,\n height: 250,\n margin: { top: 35, right: 50, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 34,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'enrichment (n-fold)',\n label_offset: 40,\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [intervals_layer_layout],\n };\n\n /**\n * (**extension**) A plot layout that shows association summary statistics, genes, and intervals-by-enrichment data.\n * This layout provides interactive matching: clicking an interval marking causes area of the scatter plot to be\n * highlighted for any annotations that match the specified category.\n * It is intended to work with data in the HuGeAMP format.\n * @alias module:LocusZoom_Layouts~intervals_association_enrichment\n * @type plot\n * @see {@link module:ext/lz-intervals-enrichment} for required extension and installation instructions\n */\n const intervals_plot_layout = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n function () {\n const base = LocusZoom.Layouts.get('panel', 'association');\n base.data_layers.unshift(intervals_highlight_layout);\n return base;\n }(),\n intervals_panel_layout,\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n\n LocusZoom.DataLayers.add('intervals_enrichment', LzIntervalsEnrichment);\n\n LocusZoom.Layouts.add('tooltip', 'intervals_enrichment', intervals_tooltip_layout);\n LocusZoom.Layouts.add('data_layer', 'intervals_enrichment', intervals_layer_layout);\n LocusZoom.Layouts.add('panel', 'intervals_enrichment', intervals_panel_layout);\n LocusZoom.Layouts.add('plot', 'intervals_association_enrichment', intervals_plot_layout);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-intervals-track.min.js b/dist/ext/lz-intervals-track.min.js index 4e676179..0f6a4fdb 100644 --- a/dist/ext/lz-intervals-track.min.js +++ b/dist/ext/lz-intervals-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.1 */ -var LzIntervalsTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var r in a)t.o(a,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>o});const a=d3,r=Symbol.for("lzXCS"),s=Symbol.for("lzYCS"),i=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function n(t){const e=t.Adapters.get("BaseUMAdapter"),n=t.Widgets.get("_Button"),o=t.Widgets.get("BaseWidget");function c(t,e){return e?`rgb(${e})`:null}const d={start_field:"start",end_field:"end",track_label_field:"state_name",track_split_field:"state_id",track_split_order:"DESC",track_split_legend_to_y_axis:2,split_tracks:!0,track_height:15,track_vertical_spacing:3,bounding_box_padding:2,always_hide_legend:!1,color:"#B8B8B8",fill_opacity:1,tooltip_positioning:"vertical"},g=t.DataLayers.get("BaseDataLayer");const _={closable:!1,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"{{intervals:state_name|htmlescape}}
    {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}"},h={namespace:{intervals:"intervals"},id:"intervals",type:"intervals",tag:"intervals",id_field:"{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}",start_field:"intervals:start",end_field:"intervals:end",track_split_field:"intervals:state_name",track_label_field:"intervals:state_name",split_tracks:!1,always_hide_legend:!0,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:state_name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],legend:[],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:_},u=t.Layouts.merge({id_field:"{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}",start_field:"intervals:chromStart",end_field:"intervals:chromEnd",track_split_field:"intervals:name",track_label_field:"intervals:name",split_tracks:!0,always_hide_legend:!1,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],tooltip:t.Layouts.merge({html:"Group: {{intervals:name|htmlescape}}
    \nRegion: {{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}}\n{{#if intervals:score}}
    \nScore: {{intervals:score|htmlescape}}{{/if}}"},_)},h),p={id:"intervals",tag:"intervals",min_height:50,height:50,margin:{top:25,right:150,bottom:5,left:50},toolbar:function(){const e=t.Layouts.get("toolbar","standard_panel");return e.widgets.push({type:"toggle_split_tracks",data_layer_id:"intervals",position:"right"}),e}(),axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},legend:{hidden:!0,orientation:"horizontal",origin:{x:50,y:0},pad_from_bottom:5},data_layers:[h]},y=t.Layouts.merge({min_height:120,height:120,data_layers:[u]},p),b={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association"),panels:[t.Layouts.get("panel","association"),t.Layouts.merge({min_height:120,height:120},p),t.Layouts.get("panel","genes")]};t.Adapters.add("IntervalLZ",class extends e{_getURL(t){const e=`?filter=id in ${this._config.source} and chromosome eq '${t.chr}' and start le ${t.end} and end ge ${t.start}`;return`${super._getURL(t)}${e}`}}),t.DataLayers.add("intervals",class extends g{constructor(e){t.Layouts.merge(e,d),super(...arguments),this._previous_categories=[],this._categories=[]}initialize(){super.initialize(),this._statusnodes_group=this.svg.group.append("g").attr("class","lz-data-layer-intervals lz-data-layer-intervals-statusnode"),this._datanodes_group=this.svg.group.append("g").attr("class","lz-data_layer-intervals")}_arrangeTrackSplit(t){const{track_split_field:e}=this.layout,a={};return t.forEach((t=>{const r=t[e];Object.prototype.hasOwnProperty.call(a,r)||(a[r]=[]),a[r].push(t)})),a}_arrangeTracksLinear(t,e=!0){if(e)return[t];const{start_field:a,end_field:r}=this.layout,s=[[]];return t.forEach(((t,e)=>{for(let e=0;e{d[t].forEach((t=>{t[r]=e(t[a]),t[i]=e(t[n]),t[s]=g*this.getTrackHeight()+o,t[l]=t[s]+c,t.track=g}))})),[g,Object.values(d).reduce(((t,e)=>t.concat(e)),[])]}getElementStatusNodeId(t){if(this.layout.split_tracks){const e="object"==typeof t?t.track:t;return`${this.getBaseId()}-statusnode-${e}`.replace(/[^\w]/g,"_")}return null}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}_applyLayoutOptions(){const t=this,e=this._base_layout,a=this.layout,r=e.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function})),s=a.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function}));if(!r)throw new Error("Interval tracks must define a `categorical_bin` color scale");const i=r.parameters.categories.length&&r.parameters.values.length,l=e.legend&&e.legend.length;if(!!i^!!l)throw new Error("To use a manually specified color scheme, both color and legend options must be set.");const n=e.color.find((function(t){return t.scale_function&&"to_rgb"===t.scale_function})),o=n&&n.field,c=this._generateCategoriesFromData(this.data,o);if(!i&&!l){const e=this._makeColorScheme(c);s.parameters.categories=c.map((function(t){return t[0]})),s.parameters.values=e,this.layout.legend=c.map((function(e,a){const r=e[0],i={shape:"rect",width:9,label:e[1],color:s.parameters.values[a]};return i[t.layout.track_split_field]=r,i}))}}render(){this._applyLayoutOptions(),this._previous_categories=this._categories;const[t,e]=this._assignTracks(this.data);this._categories=t;if(!t.every(((t,e)=>t===this._previous_categories[e])))return void this.updateSplitTrackAxis(t);const l=this._applyFilters(e);this._statusnodes_group.selectAll("rect").remove();const n=this._statusnodes_group.selectAll("rect").data(a.range(t.length));if(this.layout.split_tracks){const t=this.getTrackHeight();n.enter().append("rect").attr("class","lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared").attr("rx",this.layout.bounding_box_padding).attr("ry",this.layout.bounding_box_padding).merge(n).attr("id",(t=>this.getElementStatusNodeId(t))).attr("x",0).attr("y",(e=>e*t)).attr("width",this.parent.layout.cliparea.width).attr("height",Math.max(t-this.layout.track_vertical_spacing,1))}n.exit().remove();const o=this._datanodes_group.selectAll("rect").data(l,(t=>t[this.layout.id_field]));o.enter().append("rect").merge(o).attr("id",(t=>this.getElementId(t))).attr("x",(t=>t[r])).attr("y",(t=>t[s])).attr("width",(t=>Math.max(t[i]-t[r],1))).attr("height",this.layout.track_height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),o.exit().remove(),this._datanodes_group.call(this.applyBehaviors.bind(this)),this.parent&&this.parent.legend&&this.parent.legend.render()}_getTooltipPosition(t){return{x_min:t.data[r],x_max:t.data[i],y_min:t.data[s],y_max:t.data[l]}}updateSplitTrackAxis(t){const e=!!this.layout.track_split_legend_to_y_axis&&`y${this.layout.track_split_legend_to_y_axis}`;if(this.layout.split_tracks){const a=+t.length||0,r=+this.layout.track_height||0,s=2*(+this.layout.bounding_box_padding||0)+(+this.layout.track_vertical_spacing||0),i=a*r+(a-1)*s;this.parent.scaleHeightToData(i),e&&this.parent.legend&&(this.parent.legend.hide(),this.parent.layout.axes[e]={render:!0,ticks:[],range:{start:i-this.layout.track_height/2,end:this.layout.track_height/2}},this.layout.legend.forEach((r=>{const s=r[this.layout.track_split_field];let i=t.findIndex((t=>t===s));-1!==i&&("DESC"===this.layout.track_split_order&&(i=Math.abs(i-a-1)),this.parent.layout.axes[e].ticks.push({y:i-1,text:r.label}))})),this.layout.y_axis={axis:this.layout.track_split_legend_to_y_axis,floor:1,ceiling:a}),this.parent_plot.positionPanels()}else e&&this.parent.legend&&(this.layout.always_hide_legend||this.parent.legend.show(),this.parent.layout.axes[e]={render:!1},this.parent.render());return this}toggleSplitTracks(){return this.layout.split_tracks=!this.layout.split_tracks,this.parent.legend&&!this.layout.always_hide_legend&&(this.parent.layout.margin.bottom=5+(this.layout.split_tracks?0:this.parent.legend.layout.height+5)),this.render(),this}_makeColorScheme(t){if(t.find((t=>t[2])))return t.map((t=>c(0,t[2])));const e=t.length;return e<=15?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(233,150,122)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(50,205,50)","rgb(255,69,0)","rgb(255,0,0)"]:e<=18?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]:["rgb(212,212,212)","rgb(128,128,128)","rgb(112,48,160)","rgb(230,184,183)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,102)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,150,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]}_generateCategoriesFromData(t,e){const a=this,r=this._base_layout.legend;if(r&&r.length)return r.map((t=>[t[this.layout.track_split_field],t.label,t.color]));const s={},i=[];return t.forEach((t=>{const r=t[a.layout.track_split_field];Object.prototype.hasOwnProperty.call(s,r)||(s[r]=null,i.push([r,t[this.layout.track_label_field],t[e]]))})),i}}),t.Layouts.add("tooltip","standard_intervals",_),t.Layouts.add("data_layer","intervals",h),t.Layouts.add("data_layer","bed_intervals",u),t.Layouts.add("panel","intervals",p),t.Layouts.add("panel","bed_intervals",y),t.Layouts.add("plot","interval_association",b),t.ScaleFunctions.add("to_rgb",c),t.Widgets.add("toggle_split_tracks",class extends o{constructor(t){if(super(...arguments),t.data_layer_id||(t.data_layer_id="intervals"),!this.parent_panel.data_layers[t.data_layer_id])throw new Error("Toggle split tracks widget specifies an invalid data layer ID")}update(){const t=this.parent_panel.data_layers[this.layout.data_layer_id],e=t.layout.split_tracks?"Merge Tracks":"Split Tracks";return this.button?(this.button.setHtml(e),this.button.show(),this.parent.position(),this):(this.button=new n(this).setColor(this.layout.color).setHtml(e).setTitle("Toggle whether tracks are split apart or merged together").setOnclick((()=>{t.toggleSplitTracks(),this.scale_timeout&&clearTimeout(this.scale_timeout),this.scale_timeout=setTimeout((()=>{this.parent_panel.scaleHeightToData(),this.parent_plot.positionPanels()}),0),this.update()})),this.update())}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const o=n;LzIntervalsTrack=e.default})(); +/*! Locuszoom 0.14.0-beta.2 */ +var LzIntervalsTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var r in a)t.o(a,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>o});const a=d3,r=Symbol.for("lzXCS"),s=Symbol.for("lzYCS"),i=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function n(t){const e=t.Adapters.get("BaseUMAdapter"),n=t.Widgets.get("_Button"),o=t.Widgets.get("BaseWidget");function c(t,e){return e?`rgb(${e})`:null}const d={start_field:"start",end_field:"end",track_label_field:"state_name",track_split_field:"state_id",track_split_order:"DESC",track_split_legend_to_y_axis:2,split_tracks:!0,track_height:15,track_vertical_spacing:3,bounding_box_padding:2,always_hide_legend:!1,color:"#B8B8B8",fill_opacity:1,tooltip_positioning:"vertical"},g=t.DataLayers.get("BaseDataLayer");const _={closable:!1,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"{{intervals:state_name|htmlescape}}
    {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}"},h={namespace:{intervals:"intervals"},id:"intervals",type:"intervals",tag:"intervals",id_field:"{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}",start_field:"intervals:start",end_field:"intervals:end",track_split_field:"intervals:state_name",track_label_field:"intervals:state_name",split_tracks:!1,always_hide_legend:!0,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:state_name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],legend:[],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:_},u=t.Layouts.merge({id_field:"{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}",start_field:"intervals:chromStart",end_field:"intervals:chromEnd",track_split_field:"intervals:name",track_label_field:"intervals:name",split_tracks:!0,always_hide_legend:!1,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],tooltip:t.Layouts.merge({html:"Group: {{intervals:name|htmlescape}}
    \nRegion: {{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}}\n{{#if intervals:score}}
    \nScore: {{intervals:score|htmlescape}}{{/if}}"},_)},h),p={id:"intervals",tag:"intervals",min_height:50,height:50,margin:{top:25,right:150,bottom:5,left:70},toolbar:function(){const e=t.Layouts.get("toolbar","standard_panel");return e.widgets.push({type:"toggle_split_tracks",data_layer_id:"intervals",position:"right"}),e}(),axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},legend:{hidden:!0,orientation:"horizontal",origin:{x:50,y:0},pad_from_bottom:5},data_layers:[h]},y=t.Layouts.merge({min_height:120,height:120,data_layers:[u]},p),b={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association"),panels:[t.Layouts.get("panel","association"),t.Layouts.merge({min_height:120,height:120},p),t.Layouts.get("panel","genes")]};t.Adapters.add("IntervalLZ",class extends e{_getURL(t){const e=`?filter=id in ${this._config.source} and chromosome eq '${t.chr}' and start le ${t.end} and end ge ${t.start}`;return`${super._getURL(t)}${e}`}}),t.DataLayers.add("intervals",class extends g{constructor(e){t.Layouts.merge(e,d),super(...arguments),this._previous_categories=[],this._categories=[]}initialize(){super.initialize(),this._statusnodes_group=this.svg.group.append("g").attr("class","lz-data-layer-intervals lz-data-layer-intervals-statusnode"),this._datanodes_group=this.svg.group.append("g").attr("class","lz-data_layer-intervals")}_arrangeTrackSplit(t){const{track_split_field:e}=this.layout,a={};return t.forEach((t=>{const r=t[e];Object.prototype.hasOwnProperty.call(a,r)||(a[r]=[]),a[r].push(t)})),a}_arrangeTracksLinear(t,e=!0){if(e)return[t];const{start_field:a,end_field:r}=this.layout,s=[[]];return t.forEach(((t,e)=>{for(let e=0;e{d[t].forEach((t=>{t[r]=e(t[a]),t[i]=e(t[n]),t[s]=g*this.getTrackHeight()+o,t[l]=t[s]+c,t.track=g}))})),[g,Object.values(d).reduce(((t,e)=>t.concat(e)),[])]}getElementStatusNodeId(t){if(this.layout.split_tracks){const e="object"==typeof t?t.track:t;return`${this.getBaseId()}-statusnode-${e}`.replace(/[^\w]/g,"_")}return null}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}_applyLayoutOptions(){const t=this,e=this._base_layout,a=this.layout,r=e.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function})),s=a.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function}));if(!r)throw new Error("Interval tracks must define a `categorical_bin` color scale");const i=r.parameters.categories.length&&r.parameters.values.length,l=e.legend&&e.legend.length;if(!!i^!!l)throw new Error("To use a manually specified color scheme, both color and legend options must be set.");const n=e.color.find((function(t){return t.scale_function&&"to_rgb"===t.scale_function})),o=n&&n.field,c=this._generateCategoriesFromData(this.data,o);if(!i&&!l){const e=this._makeColorScheme(c);s.parameters.categories=c.map((function(t){return t[0]})),s.parameters.values=e,this.layout.legend=c.map((function(e,a){const r=e[0],i={shape:"rect",width:9,label:e[1],color:s.parameters.values[a]};return i[t.layout.track_split_field]=r,i}))}}render(){this._applyLayoutOptions(),this._previous_categories=this._categories;const[t,e]=this._assignTracks(this.data);this._categories=t;if(!t.every(((t,e)=>t===this._previous_categories[e])))return void this.updateSplitTrackAxis(t);const l=this._applyFilters(e);this._statusnodes_group.selectAll("rect").remove();const n=this._statusnodes_group.selectAll("rect").data(a.range(t.length));if(this.layout.split_tracks){const t=this.getTrackHeight();n.enter().append("rect").attr("class","lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared").attr("rx",this.layout.bounding_box_padding).attr("ry",this.layout.bounding_box_padding).merge(n).attr("id",(t=>this.getElementStatusNodeId(t))).attr("x",0).attr("y",(e=>e*t)).attr("width",this.parent.layout.cliparea.width).attr("height",Math.max(t-this.layout.track_vertical_spacing,1))}n.exit().remove();const o=this._datanodes_group.selectAll("rect").data(l,(t=>t[this.layout.id_field]));o.enter().append("rect").merge(o).attr("id",(t=>this.getElementId(t))).attr("x",(t=>t[r])).attr("y",(t=>t[s])).attr("width",(t=>Math.max(t[i]-t[r],1))).attr("height",this.layout.track_height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),o.exit().remove(),this._datanodes_group.call(this.applyBehaviors.bind(this)),this.parent&&this.parent.legend&&this.parent.legend.render()}_getTooltipPosition(t){return{x_min:t.data[r],x_max:t.data[i],y_min:t.data[s],y_max:t.data[l]}}updateSplitTrackAxis(t){const e=!!this.layout.track_split_legend_to_y_axis&&`y${this.layout.track_split_legend_to_y_axis}`;if(this.layout.split_tracks){const a=+t.length||0,r=+this.layout.track_height||0,s=2*(+this.layout.bounding_box_padding||0)+(+this.layout.track_vertical_spacing||0),i=a*r+(a-1)*s;this.parent.scaleHeightToData(i),e&&this.parent.legend&&(this.parent.legend.hide(),this.parent.layout.axes[e]={render:!0,ticks:[],range:{start:i-this.layout.track_height/2,end:this.layout.track_height/2}},this.layout.legend.forEach((r=>{const s=r[this.layout.track_split_field];let i=t.findIndex((t=>t===s));-1!==i&&("DESC"===this.layout.track_split_order&&(i=Math.abs(i-a-1)),this.parent.layout.axes[e].ticks.push({y:i-1,text:r.label}))})),this.layout.y_axis={axis:this.layout.track_split_legend_to_y_axis,floor:1,ceiling:a}),this.parent_plot.positionPanels()}else e&&this.parent.legend&&(this.layout.always_hide_legend||this.parent.legend.show(),this.parent.layout.axes[e]={render:!1},this.parent.render());return this}toggleSplitTracks(){return this.layout.split_tracks=!this.layout.split_tracks,this.parent.legend&&!this.layout.always_hide_legend&&(this.parent.layout.margin.bottom=5+(this.layout.split_tracks?0:this.parent.legend.layout.height+5)),this.render(),this}_makeColorScheme(t){if(t.find((t=>t[2])))return t.map((t=>c(0,t[2])));const e=t.length;return e<=15?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(233,150,122)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(50,205,50)","rgb(255,69,0)","rgb(255,0,0)"]:e<=18?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]:["rgb(212,212,212)","rgb(128,128,128)","rgb(112,48,160)","rgb(230,184,183)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,102)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,150,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]}_generateCategoriesFromData(t,e){const a=this,r=this._base_layout.legend;if(r&&r.length)return r.map((t=>[t[this.layout.track_split_field],t.label,t.color]));const s={},i=[];return t.forEach((t=>{const r=t[a.layout.track_split_field];Object.prototype.hasOwnProperty.call(s,r)||(s[r]=null,i.push([r,t[this.layout.track_label_field],t[e]]))})),i}}),t.Layouts.add("tooltip","standard_intervals",_),t.Layouts.add("data_layer","intervals",h),t.Layouts.add("data_layer","bed_intervals",u),t.Layouts.add("panel","intervals",p),t.Layouts.add("panel","bed_intervals",y),t.Layouts.add("plot","interval_association",b),t.ScaleFunctions.add("to_rgb",c),t.Widgets.add("toggle_split_tracks",class extends o{constructor(t){if(super(...arguments),t.data_layer_id||(t.data_layer_id="intervals"),!this.parent_panel.data_layers[t.data_layer_id])throw new Error("Toggle split tracks widget specifies an invalid data layer ID")}update(){const t=this.parent_panel.data_layers[this.layout.data_layer_id],e=t.layout.split_tracks?"Merge Tracks":"Split Tracks";return this.button?(this.button.setHtml(e),this.button.show(),this.parent.position(),this):(this.button=new n(this).setColor(this.layout.color).setHtml(e).setTitle("Toggle whether tracks are split apart or merged together").setOnclick((()=>{t.toggleSplitTracks(),this.scale_timeout&&clearTimeout(this.scale_timeout),this.scale_timeout=setTimeout((()=>{this.parent_panel.scaleHeightToData(),this.parent_plot.positionPanels()}),0),this.update()})),this.update())}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const o=n;LzIntervalsTrack=e.default})(); //# sourceMappingURL=lz-intervals-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-track.min.js.map b/dist/ext/lz-intervals-track.min.js.map index 607d428b..21015911 100644 --- a/dist/ext/lz-intervals-track.min.js.map +++ b/dist/ext/lz-intervals-track.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/ext/lz-intervals-track.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","XCS","Symbol","for","YCS","XCE","YCE","install","LocusZoom","BaseUMAdapter","Adapters","_Button","Widgets","_BaseWidget","to_rgb","parameters","value","default_layout","start_field","end_field","track_label_field","track_split_field","track_split_order","track_split_legend_to_y_axis","split_tracks","track_height","track_vertical_spacing","bounding_box_padding","always_hide_legend","color","fill_opacity","tooltip_positioning","BaseLayer","DataLayers","intervals_tooltip_layout","closable","show","or","hide","and","html","intervals_layer_layout","namespace","id","type","tag","id_field","field","scale_function","categories","values","null_value","legend","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip","bed_intervals_layer_layout","Layouts","merge","intervals_panel_layout","min_height","height","margin","top","right","bottom","left","toolbar","l","widgets","push","data_layer_id","position","axes","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","hidden","orientation","origin","x","y","pad_from_bottom","data_layers","bed_intervals_panel_layout","intervals_plot_layout","state","width","responsive_resize","min_region_scale","max_region_scale","panels","add","request_options","query","this","_config","source","chr","end","start","super","_getURL","layout","arguments","_previous_categories","_categories","initialize","_statusnodes_group","svg","group","append","attr","_datanodes_group","data","result","forEach","item","item_key","allow_overlap","grouped_data","index","i","length","row_to_test","last_item","x_scale","parent","_arrangeTrackSplit","_arrangeTracksLinear","keys","reverse","row_index","getTrackHeight","track","reduce","acc","val","concat","element","getBaseId","replace","self","base_layout","_base_layout","render_layout","base_color_scale","find","color_scale","Error","has_colors","has_legend","rgb_option","rgb_field","known_categories","_generateCategoriesFromData","colors","_makeColorScheme","map","pair","shape","label","_applyLayoutOptions","assigned_data","_assignTracks","every","updateSplitTrackAxis","track_data","_applyFilters","selectAll","remove","status_nodes","enter","d","getElementStatusNodeId","cliparea","Math","max","exit","data_nodes","getElementId","resolveScalableParameter","applyBehaviors","bind","render","x_min","x_max","y_min","y_max","legend_axis","tracks","track_spacing","target_height","scaleHeightToData","ticks","range","findIndex","abs","text","y_axis","axis","floor","ceiling","parent_plot","positionPanels","category_info","n_categories","unique_ids","ScaleFunctions","parent_panel","data_layer","button","setHtml","setColor","setTitle","setOnclick","toggleSplitTracks","scale_timeout","clearTimeout","setTimeout","update","use"],"mappings":"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","file":"ext/lz-intervals-track.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Interval annotation track (for chromatin state, etc). Useful for BED file data with non-overlapping intervals.\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~IntervalLZ}\n * * {@link module:LocusZoom_Widgets~toggle_split_tracks}\n * * {@link module:LocusZoom_ScaleFunctions~to_rgb}\n * * {@link module:LocusZoom_DataLayers~intervals}\n * * {@link module:LocusZoom_Layouts~standard_intervals}\n * * {@link module:LocusZoom_Layouts~bed_intervals_layer}\n * * {@link module:LocusZoom_Layouts~intervals_layer}\n * * {@link module:LocusZoom_Layouts~intervals}\n * * {@link module:LocusZoom_Layouts~bed_intervals}\n * * {@link module:LocusZoom_Layouts~interval_association}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import IntervalsTrack from 'locuszoom/esm/ext/lz-intervals-track';\n * LocusZoom.use(IntervalsTrack);\n * ```\n *\n * Then use the features made available by this extension. (see demos and documentation for guidance)\n * @module\n */\n\nimport * as d3 from 'd3';\n\n\n// Coordinates (start, end) are cached to facilitate rendering\nconst XCS = Symbol.for('lzXCS');\nconst YCS = Symbol.for('lzYCS');\nconst XCE = Symbol.for('lzXCE');\nconst YCE = Symbol.for('lzYCE');\n\n\nfunction install (LocusZoom) {\n const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter');\n const _Button = LocusZoom.Widgets.get('_Button');\n const _BaseWidget = LocusZoom.Widgets.get('BaseWidget');\n\n /**\n * (**extension**) Retrieve Interval Annotation Data (e.g. BED Tracks), as fetched from the LocusZoom API server (or compatible)\n * @public\n * @alias module:LocusZoom_Adapters~IntervalLZ\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n * @param {number} config.params.source The numeric ID for a specific dataset as assigned by the API server\n */\n class IntervalLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const source = this._config.source;\n const query = `?filter=id in ${source} and chromosome eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}`;\n\n const base = super._getURL(request_options);\n return `${base}${query}`;\n }\n }\n\n /**\n * (**extension**) Button to toggle split tracks mode in an intervals track. This button only works as a panel-level toolbar\n * and when used with an intervals data layer from this extension.\n * @alias module:LocusZoom_Widgets~toggle_split_tracks\n * @see module:LocusZoom_Widgets~BaseWidget\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n class ToggleSplitTracks extends _BaseWidget {\n /**\n * @param {string} layout.data_layer_id The ID of the data layer that this button is intended to control.\n */\n constructor(layout) {\n super(...arguments);\n if (!layout.data_layer_id) {\n layout.data_layer_id = 'intervals';\n }\n if (!this.parent_panel.data_layers[layout.data_layer_id]) {\n throw new Error('Toggle split tracks widget specifies an invalid data layer ID');\n }\n }\n\n update() {\n const data_layer = this.parent_panel.data_layers[this.layout.data_layer_id];\n const html = data_layer.layout.split_tracks ? 'Merge Tracks' : 'Split Tracks';\n if (this.button) {\n this.button.setHtml(html);\n this.button.show();\n this.parent.position();\n return this;\n } else {\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(html)\n .setTitle('Toggle whether tracks are split apart or merged together')\n .setOnclick(() => {\n data_layer.toggleSplitTracks();\n // FIXME: the timeout calls to scale and position (below) cause full ~5 additional re-renders\n // If we can remove these it will greatly speed up re-rendering.\n // The key problem here is that the height is apparently not known in advance and is determined after re-render.\n if (this.scale_timeout) {\n clearTimeout(this.scale_timeout);\n }\n this.scale_timeout = setTimeout(() => {\n this.parent_panel.scaleHeightToData();\n this.parent_plot.positionPanels();\n }, 0);\n this.update();\n });\n return this.update();\n }\n }\n }\n\n\n /**\n * (**extension**) Convert a value \"\"rr,gg,bb\" (if given) to a css-friendly color string: \"rgb(rr,gg,bb)\".\n * This is tailored specifically to the color specification format embraced by the BED file standard.\n * @alias module:LocusZoom_ScaleFunctions~to_rgb\n * @param {Object} parameters This function has no defined configuration options\n * @param {String|null} value The value to convert to rgb\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n function to_rgb(parameters, value) {\n return value ? `rgb(${value})` : null;\n }\n\n const default_layout = {\n start_field: 'start',\n end_field: 'end',\n track_label_field: 'state_name', // Used to label items on the y-axis\n // Used to uniquely identify tracks for coloring. This tends to lead to more stable coloring/sorting\n // than using the label field- eg, state_ids allow us to set global colors across the entire dataset,\n // not just choose unique colors within a particular narrow region. (where changing region might lead to more\n // categories and different colors)\n track_split_field: 'state_id',\n track_split_order: 'DESC',\n track_split_legend_to_y_axis: 2,\n split_tracks: true,\n track_height: 15,\n track_vertical_spacing: 3,\n bounding_box_padding: 2,\n always_hide_legend: false,\n color: '#B8B8B8',\n fill_opacity: 1,\n tooltip_positioning: 'vertical',\n };\n\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n\n /**\n * (**extension**) Implements a data layer that will render interval annotation tracks (intervals must provide start and end values)\n * Each interval (such as from a BED file) will be rendered as a rectangle. All spans can be rendered on the same\n * row, or each (auto-detected) category can be rendered as one row per category.\n *\n * This layer is intended to work with a variety of datasets with special requirements. As such, it has a lot\n * of configuration options devoted to identifying how to fill in missing information (such as color)\n *\n * @alias module:LocusZoom_DataLayers~intervals\n * @see module:LocusZoom_DataLayers~BaseDataLayer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n class LzIntervalsTrack extends BaseLayer {\n /**\n * @param {string} [layout.start_field='start'] The field that defines interval start position\n * @param {string} [layout.end_field='end'] The field that defines interval end position\n * @param {string} [layout.track_label_field='state_name'] Used to label items on the y-axis\n * @param {string} [layout.track_split_field='state_id'] Used to define categories on the y-axis. It is usually most convenient to use\n * the same value for state_field and label_field (eg 1:1 correspondence).\n * @param {*|'DESC'} [layout.track_split_order='DESC'] When in split tracks mode, should categories be shown in\n * the order given, or descending order\n * @param {number} [layout.track_split_legend_to_y_axis=2]\n * @param {boolean} [layout.split_tracks=true] Whether to show tracks as merged (one row) or split (many rows)\n * on initial render.\n * @param {number} [layout.track_height=15] The height of each interval rectangle, in px\n * @param {number} [layout.track_vertical_spacing=3]\n * @param {number} [layout.bounding_box_padding=2]\n * @param {boolean} [layout.always_hide_legend=false] Normally the legend is shown in merged mode and hidden\n * in split mode. For datasets with a very large number of categories, it may make sense to hide the legend at all times.\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#B8B8B8'] The color of each datum rectangle\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1]\n * @param {string} [layout.tooltip_positioning='vertical']\n */\n constructor(layout) {\n LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n this._previous_categories = [];\n this._categories = [];\n }\n\n initialize() {\n super.initialize();\n this._statusnodes_group = this.svg.group.append('g')\n .attr('class', 'lz-data-layer-intervals lz-data-layer-intervals-statusnode');\n this._datanodes_group = this.svg.group.append('g')\n .attr('class', 'lz-data_layer-intervals');\n }\n\n /**\n * Split data into tracks such that anything with a common grouping field is in the same track\n * @param data\n * @return {unknown[]}\n * @private\n */\n _arrangeTrackSplit(data) {\n const {track_split_field} = this.layout;\n const result = {};\n data.forEach((item) => {\n const item_key = item[track_split_field];\n if (!Object.prototype.hasOwnProperty.call(result, item_key)) {\n result[item_key] = [];\n }\n result[item_key].push(item);\n });\n return result;\n }\n\n /**\n * Split data into rows using a simple greedy algorithm such that no two items overlap (share same interval)\n * Assumes that the data are sorted so item1.start always <= item2.start.\n *\n * This function can also simply return all data on a single row. This functionality may become configurable\n * in the future but for now reflects a lack of clarity in the requirements/spec. The code to split\n * overlapping items is present but may not see direct use.\n */\n _arrangeTracksLinear(data, allow_overlap = true) {\n if (allow_overlap) {\n // If overlap is allowed, then all the data can live on a single row\n return [data];\n }\n\n // ASSUMPTION: Data is given to us already sorted by start position to facilitate grouping.\n // We do not sort here because JS \"sort\" is not stable- if there are many intervals that overlap, then we\n // can get different layouts (number/order of rows) on each call to \"render\".\n //\n // At present, we decide how to update the y-axis based on whether current and former number of rows are\n // the same. An unstable sort leads to layout thrashing/too many re-renders. FIXME: don't rely on counts\n const {start_field, end_field} = this.layout;\n\n const grouped_data = [[]]; // Prevent two items from colliding by rendering them to different rows, like genes\n data.forEach((item, index) => {\n for (let i = 0; i < grouped_data.length; i++) {\n // Iterate over all rows of the\n const row_to_test = grouped_data[i];\n const last_item = row_to_test[row_to_test.length - 1];\n // Some programs report open intervals, eg 0-1,1-2,2-3; these points are not considered to overlap (hence the test isn't \"<=\")\n const has_overlap = last_item && (item[start_field] < last_item[end_field]) && (last_item[start_field] < item[end_field]);\n if (!has_overlap) {\n // If there is no overlap, add item to current row, and move on to the next item\n row_to_test.push(item);\n return;\n }\n }\n // If this item would collide on all existing rows, create a new row\n grouped_data.push([item]);\n });\n return grouped_data;\n }\n\n /**\n * Annotate each item with the track number, and return.\n * @param {Object[]}data\n * @private\n * @return [String[], Object[]] Return the categories and the data array\n */\n _assignTracks(data) {\n // Flatten the grouped data.\n const {x_scale} = this.parent;\n const {start_field, end_field, bounding_box_padding, track_height} = this.layout;\n\n const grouped_data = this.layout.split_tracks ? this._arrangeTrackSplit(data) : this._arrangeTracksLinear(data, true);\n const categories = Object.keys(grouped_data);\n if (this.layout.track_split_order === 'DESC') {\n categories.reverse();\n }\n\n categories.forEach((key, row_index) => {\n const row = grouped_data[key];\n row.forEach((item) => {\n item[XCS] = x_scale(item[start_field]);\n item[XCE] = x_scale(item[end_field]);\n item[YCS] = row_index * this.getTrackHeight() + bounding_box_padding;\n item[YCE] = item[YCS] + track_height;\n // Store the row ID, so that clicking on a point can find the right status node (big highlight box)\n item.track = row_index;\n });\n });\n // We're mutating elements of the original data array as a side effect: the return value here is\n // interchangeable with `this.data` for subsequent usages\n // TODO: Can replace this with array.flat once polyfill support improves\n return [categories, Object.values(grouped_data).reduce((acc, val) => acc.concat(val), [])];\n }\n\n /**\n * When we are in \"split tracks mode\", it's convenient to wrap all individual annotations with a shared\n * highlight box that wraps everything on that row.\n *\n * This is done automatically by the \"setElementStatus\" code, if this function returns a non-null value\n *\n * To define shared highlighting on the track split field define the status node id override\n * to generate an ID common to the track when we're actively splitting data out to separate tracks\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n if (this.layout.split_tracks) {\n // Data nodes are bound to data objects, but the \"status_nodes\" selection is bound to numeric row IDs\n const track = typeof element === 'object' ? element.track : element;\n const base = `${this.getBaseId()}-statusnode-${track}`;\n return base.replace(/[^\\w]/g, '_');\n }\n // In merged tracks mode, there is no separate status node\n return null;\n }\n\n // Helper function to sum layout values to derive total height for a single interval track\n getTrackHeight() {\n return this.layout.track_height\n + this.layout.track_vertical_spacing\n + (2 * this.layout.bounding_box_padding);\n }\n\n // Modify the layout as necessary to ensure that appropriate color, label, and legend options are available\n // Even when not displayed, the legend is used to generate the y-axis ticks\n _applyLayoutOptions() {\n const self = this;\n const base_layout = this._base_layout;\n const render_layout = this.layout;\n const base_color_scale = base_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'categorical_bin';\n });\n const color_scale = render_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'categorical_bin';\n });\n if (!base_color_scale) {\n // This can be a placeholder (empty categories & values), but it needs to be there\n throw new Error('Interval tracks must define a `categorical_bin` color scale');\n }\n\n const has_colors = base_color_scale.parameters.categories.length && base_color_scale.parameters.values.length;\n const has_legend = base_layout.legend && base_layout.legend.length;\n\n if (!!has_colors ^ !!has_legend) {\n // Don't allow color OR legend to be set manually. It must be both, or neither.\n throw new Error('To use a manually specified color scheme, both color and legend options must be set.');\n }\n\n // Harvest any information about an explicit color field that should be considered when generating colors\n const rgb_option = base_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'to_rgb';\n });\n const rgb_field = rgb_option && rgb_option.field;\n\n // Auto-generate legend based on data\n const known_categories = this._generateCategoriesFromData(this.data, rgb_field); // [id, label, itemRgb] items\n\n if (!has_colors && !has_legend) {\n // If no color scheme pre-defined, then make a color scheme that is appropriate and apply to the plot\n // The legend must match the color scheme. If we generate one, then we must generate both.\n\n const colors = this._makeColorScheme(known_categories);\n color_scale.parameters.categories = known_categories.map(function (item) {\n return item[0];\n });\n color_scale.parameters.values = colors;\n\n this.layout.legend = known_categories.map(function (pair, index) {\n const id = pair[0];\n const label = pair[1];\n const item_color = color_scale.parameters.values[index];\n const item = { shape: 'rect', width: 9, label: label, color: item_color };\n item[self.layout.track_split_field] = id;\n return item;\n });\n }\n }\n\n // Implement the main render function\n render() {\n //// Autogenerate layout options if not provided\n this._applyLayoutOptions();\n\n // Determine the appropriate layout for tracks. Store the previous categories (y axis ticks) to decide\n // whether the axis needs to be re-rendered.\n this._previous_categories = this._categories;\n const [categories, assigned_data] = this._assignTracks(this.data);\n this._categories = categories;\n // Update the legend axis if the number of ticks changed\n const labels_changed = !categories.every( (item, index) => item === this._previous_categories[index]);\n if (labels_changed) {\n this.updateSplitTrackAxis(categories);\n return;\n }\n\n // Apply filters to only render a specified set of points. Hidden fields will still be given space to render, but not shown.\n const track_data = this._applyFilters(assigned_data);\n\n // Clear before every render so that, eg, highlighting doesn't persist if we load a region with different\n // categories (row 2 might be a different category and it's confusing if the row stays highlighted but changes meaning)\n // Highlighting will automatically get added back if it actually makes sense, courtesy of setElementStatus,\n // if a selected item is still in view after the new region loads.\n this._statusnodes_group.selectAll('rect')\n .remove();\n\n // Reselect in order to add new data\n const status_nodes = this._statusnodes_group.selectAll('rect')\n .data(d3.range(categories.length));\n\n if (this.layout.split_tracks) {\n // Status nodes: a big highlight box around all items of the same type. Used in split tracks mode,\n // because everything on the same row is the same category and a group makes sense\n // There are no status nodes in merged mode, because the same row contains many kinds of things\n\n // Status nodes are 1 per row, so \"data\" can just be a dummy list of possible row IDs\n // Each status node is a box that runs the length of the panel and receives a special \"colored box\" css\n // style when selected\n const height = this.getTrackHeight();\n status_nodes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared')\n .attr('rx', this.layout.bounding_box_padding)\n .attr('ry', this.layout.bounding_box_padding)\n .merge(status_nodes)\n .attr('id', (d) => this.getElementStatusNodeId(d))\n .attr('x', 0)\n .attr('y', (d) => (d * height))\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', Math.max(height - this.layout.track_vertical_spacing, 1));\n }\n status_nodes.exit()\n .remove();\n\n // Draw rectangles for the data (intervals)\n const data_nodes = this._datanodes_group.selectAll('rect')\n .data(track_data, (d) => d[this.layout.id_field]);\n\n data_nodes.enter()\n .append('rect')\n .merge(data_nodes)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => d[XCS])\n .attr('y', (d) => d[YCS])\n .attr('width', (d) => Math.max(d[XCE] - d[XCS], 1))\n .attr('height', this.layout.track_height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n data_nodes.exit()\n .remove();\n\n this._datanodes_group\n .call(this.applyBehaviors.bind(this));\n\n // The intervals track allows legends to be dynamically generated, in which case space can only be\n // allocated after the panel has been rendered.\n if (this.parent && this.parent.legend) {\n this.parent.legend.render();\n }\n }\n\n _getTooltipPosition(tooltip) {\n return {\n x_min: tooltip.data[XCS],\n x_max: tooltip.data[XCE],\n y_min: tooltip.data[YCS],\n y_max: tooltip.data[YCE],\n };\n }\n\n // Redraw split track axis or hide it, and show/hide the legend, as determined\n // by current layout parameters and data\n updateSplitTrackAxis(categories) {\n const legend_axis = this.layout.track_split_legend_to_y_axis ? `y${this.layout.track_split_legend_to_y_axis}` : false;\n if (this.layout.split_tracks) {\n const tracks = +categories.length || 0;\n const track_height = +this.layout.track_height || 0;\n const track_spacing = 2 * (+this.layout.bounding_box_padding || 0) + (+this.layout.track_vertical_spacing || 0);\n const target_height = (tracks * track_height) + ((tracks - 1) * track_spacing);\n this.parent.scaleHeightToData(target_height);\n if (legend_axis && this.parent.legend) {\n this.parent.legend.hide();\n this.parent.layout.axes[legend_axis] = {\n render: true,\n ticks: [],\n range: {\n start: (target_height - (this.layout.track_height / 2)),\n end: (this.layout.track_height / 2),\n },\n };\n // There is a very tight coupling between the display directives: each legend item must identify a key\n // field for unique tracks. (Typically this is `state_id`, the same key field used to assign unique colors)\n // The list of unique keys corresponds to the order along the y-axis\n this.layout.legend.forEach((element) => {\n const key = element[this.layout.track_split_field];\n let track = categories.findIndex((item) => item === key);\n if (track !== -1) {\n if (this.layout.track_split_order === 'DESC') {\n track = Math.abs(track - tracks - 1);\n }\n this.parent.layout.axes[legend_axis].ticks.push({\n y: track - 1,\n text: element.label,\n });\n }\n });\n this.layout.y_axis = {\n axis: this.layout.track_split_legend_to_y_axis,\n floor: 1,\n ceiling: tracks,\n };\n }\n // This will trigger a re-render\n this.parent_plot.positionPanels();\n } else {\n if (legend_axis && this.parent.legend) {\n if (!this.layout.always_hide_legend) {\n this.parent.legend.show();\n }\n this.parent.layout.axes[legend_axis] = { render: false };\n this.parent.render();\n }\n }\n return this;\n }\n\n // Method to not only toggle the split tracks boolean but also update\n // necessary display values to animate a complete merge/split\n toggleSplitTracks() {\n this.layout.split_tracks = !this.layout.split_tracks;\n if (this.parent.legend && !this.layout.always_hide_legend) {\n this.parent.layout.margin.bottom = 5 + (this.layout.split_tracks ? 0 : this.parent.legend.layout.height + 5);\n }\n this.render();\n return this;\n }\n\n // Choose an appropriate color scheme based on the number of items in the track, and whether or not we are\n // using explicitly provided itemRgb information\n _makeColorScheme(category_info) {\n // If at least one element has an explicit itemRgb, assume the entire dataset has colors. BED intervals require rgb triplets,so assume that colors will always be \"r,g,b\" format.\n const has_explicit_colors = category_info.find((item) => item[2]);\n if (has_explicit_colors) {\n return category_info.map((item) => to_rgb({}, item[2]));\n }\n\n // Use a set of color schemes for common 15, 18, or 25 state models, as specified from:\n // https://egg2.wustl.edu/roadmap/web_portal/chr_state_learning.html\n // These are actually reversed so that dim colors come first, on the premise that usually these are the\n // most common states\n const n_categories = category_info.length;\n if (n_categories <= 15) {\n return ['rgb(212,212,212)', 'rgb(192,192,192)', 'rgb(128,128,128)', 'rgb(189,183,107)', 'rgb(233,150,122)', 'rgb(205,92,92)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,0)', 'rgb(194,225,5)', 'rgb(0,100,0)', 'rgb(0,128,0)', 'rgb(50,205,50)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n } else if (n_categories <= 18) {\n return ['rgb(212,212,212)', 'rgb(192,192,192)', 'rgb(128,128,128)', 'rgb(189,183,107)', 'rgb(205,92,92)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,0)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(0,100,0)', 'rgb(0,128,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n } else {\n // If there are more than 25 categories, the interval layer will fall back to the 'null value' option\n return ['rgb(212,212,212)', 'rgb(128,128,128)', 'rgb(112,48,160)', 'rgb(230,184,183)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,102)', 'rgb(255,255,0)', 'rgb(255,255,0)', 'rgb(255,255,0)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(0,150,0)', 'rgb(0,128,0)', 'rgb(0,128,0)', 'rgb(0,128,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n }\n }\n\n /**\n * Find all of the unique tracks (a combination of name and ID information)\n * @param {Object} data\n * @param {String} [rgb_field] A field that contains an RGB value. Aimed at BED files with an itemRgb column\n * @private\n * @returns {Array} All [unique_id, label, color] pairs in data. The unique_id is the thing used to define groupings\n * most unambiguously.\n */\n _generateCategoriesFromData(data, rgb_field) {\n const self = this;\n // Use the hard-coded legend if available (ignoring any mods on re-render)\n const legend = this._base_layout.legend;\n if (legend && legend.length) {\n return legend.map((item) => [item[this.layout.track_split_field], item.label, item.color]);\n }\n\n // Generate options from data, if no preset legend exists\n const unique_ids = {}; // make categories unique\n const categories = [];\n\n data.forEach((item) => {\n const id = item[self.layout.track_split_field];\n if (!Object.prototype.hasOwnProperty.call(unique_ids, id)) {\n unique_ids[id] = null;\n // If rgbfield is null, then the last entry is undefined/null as well\n categories.push([id, item[this.layout.track_label_field], item[rgb_field]]);\n }\n });\n return categories;\n }\n }\n\n /**\n * (**extension**) A basic tooltip with information to be shown over an intervals datum\n * @alias module:LocusZoom_Layouts~standard_intervals\n * @type tooltip\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_tooltip_layout = {\n closable: false,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{intervals:state_name|htmlescape}}
    {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}',\n };\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for chromHMM output,\n * in which various states are assigned numeric state IDs and (<= as many) text state names.\n *\n * This layout is deprecated; most usages would be better served by the bed_intervals_layer layout instead.\n * @alias module:LocusZoom_Layouts~intervals_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_layer_layout = {\n namespace: { 'intervals': 'intervals' },\n id: 'intervals',\n type: 'intervals',\n tag: 'intervals',\n id_field: '{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}',\n start_field: 'intervals:start',\n end_field: 'intervals:end',\n track_split_field: 'intervals:state_name',\n track_label_field: 'intervals:state_name',\n split_tracks: false,\n always_hide_legend: true,\n color: [\n {\n // If present, an explicit color field will override any other option (and be used to auto-generate legend)\n field: 'intervals:itemRgb',\n scale_function: 'to_rgb',\n },\n {\n // TODO: Consider changing this to stable_choice in the future, for more stable coloring\n field: 'intervals:state_name',\n scale_function: 'categorical_bin',\n parameters: {\n // Placeholder. Empty categories and values will automatically be filled in when new data loads.\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n },\n ],\n legend: [], // Placeholder; auto-filled when data loads.\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: intervals_tooltip_layout,\n };\n\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for standard BED3+ files and the field names emitted by the LzParsers extension.\n * @alias module:LocusZoom_Layouts~bed_intervals_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const bed_intervals_layer_layout = LocusZoom.Layouts.merge({\n id_field: '{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}',\n start_field: 'intervals:chromStart',\n end_field: 'intervals:chromEnd',\n track_split_field: 'intervals:name',\n track_label_field: 'intervals:name',\n split_tracks: true,\n always_hide_legend: false,\n color: [\n {\n // If present, an explicit color field will override any other option (and be used to auto-generate legend)\n field: 'intervals:itemRgb',\n scale_function: 'to_rgb',\n },\n {\n // TODO: Consider changing this to stable_choice in the future, for more stable coloring\n field: 'intervals:name',\n scale_function: 'categorical_bin',\n parameters: {\n // Placeholder. Empty categories and values will automatically be filled in when new data loads.\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n },\n ],\n tooltip: LocusZoom.Layouts.merge({\n html: `Group: {{intervals:name|htmlescape}}
    \nRegion: {{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}}\n{{#if intervals:score}}
    \nScore: {{intervals:score|htmlescape}}{{/if}}`,\n }, intervals_tooltip_layout),\n }, intervals_layer_layout);\n\n\n /**\n * (**extension**) A panel containing an intervals data layer, eg for BED tracks. This is a legacy layout whose field names were specific to one partner site.\n * @alias module:LocusZoom_Layouts~intervals\n * @type panel\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_panel_layout = {\n id: 'intervals',\n tag: 'intervals',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 150, bottom: 5, left: 50 },\n toolbar: (function () {\n const l = LocusZoom.Layouts.get('toolbar', 'standard_panel');\n l.widgets.push({\n type: 'toggle_split_tracks',\n data_layer_id: 'intervals',\n position: 'right',\n });\n return l;\n })(),\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n legend: {\n hidden: true,\n orientation: 'horizontal',\n origin: { x: 50, y: 0 },\n pad_from_bottom: 5,\n },\n data_layers: [intervals_layer_layout],\n };\n\n /**\n * (**extension**) A panel containing an intervals data layer, eg for BED tracks. These field names match those returned by the LzParsers extension.\n * @alias module:LocusZoom_Layouts~bed_intervals\n * @type panel\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const bed_intervals_panel_layout = LocusZoom.Layouts.merge({\n // Normal BED tracks show the panel legend in collapsed mode!\n min_height: 120,\n height: 120,\n data_layers: [bed_intervals_layer_layout],\n }, intervals_panel_layout);\n\n /**\n * (**extension**) A plot layout that shows association summary statistics, genes, and interval data. This example assumes\n * chromHMM data. (see panel layout) Few people will use the full intervals plot layout directly outside of an example.\n * @alias module:LocusZoom_Layouts~interval_association\n * @type plot\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_plot_layout = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n LocusZoom.Layouts.get('panel', 'association'),\n LocusZoom.Layouts.merge({ min_height: 120, height: 120 }, intervals_panel_layout),\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n\n LocusZoom.Adapters.add('IntervalLZ', IntervalLZ);\n LocusZoom.DataLayers.add('intervals', LzIntervalsTrack);\n\n LocusZoom.Layouts.add('tooltip', 'standard_intervals', intervals_tooltip_layout);\n LocusZoom.Layouts.add('data_layer', 'intervals', intervals_layer_layout);\n LocusZoom.Layouts.add('data_layer', 'bed_intervals', bed_intervals_layer_layout);\n LocusZoom.Layouts.add('panel', 'intervals', intervals_panel_layout);\n LocusZoom.Layouts.add('panel', 'bed_intervals', bed_intervals_panel_layout);\n LocusZoom.Layouts.add('plot', 'interval_association', intervals_plot_layout);\n\n LocusZoom.ScaleFunctions.add('to_rgb', to_rgb);\n\n LocusZoom.Widgets.add('toggle_split_tracks', ToggleSplitTracks);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/ext/lz-intervals-track.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","XCS","Symbol","for","YCS","XCE","YCE","install","LocusZoom","BaseUMAdapter","Adapters","_Button","Widgets","_BaseWidget","to_rgb","parameters","value","default_layout","start_field","end_field","track_label_field","track_split_field","track_split_order","track_split_legend_to_y_axis","split_tracks","track_height","track_vertical_spacing","bounding_box_padding","always_hide_legend","color","fill_opacity","tooltip_positioning","BaseLayer","DataLayers","intervals_tooltip_layout","closable","show","or","hide","and","html","intervals_layer_layout","namespace","id","type","tag","id_field","field","scale_function","categories","values","null_value","legend","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip","bed_intervals_layer_layout","Layouts","merge","intervals_panel_layout","min_height","height","margin","top","right","bottom","left","toolbar","l","widgets","push","data_layer_id","position","axes","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","hidden","orientation","origin","x","y","pad_from_bottom","data_layers","bed_intervals_panel_layout","intervals_plot_layout","state","width","responsive_resize","min_region_scale","max_region_scale","panels","add","request_options","query","this","_config","source","chr","end","start","super","_getURL","layout","arguments","_previous_categories","_categories","initialize","_statusnodes_group","svg","group","append","attr","_datanodes_group","data","result","forEach","item","item_key","allow_overlap","grouped_data","index","i","length","row_to_test","last_item","x_scale","parent","_arrangeTrackSplit","_arrangeTracksLinear","keys","reverse","row_index","getTrackHeight","track","reduce","acc","val","concat","element","getBaseId","replace","self","base_layout","_base_layout","render_layout","base_color_scale","find","color_scale","Error","has_colors","has_legend","rgb_option","rgb_field","known_categories","_generateCategoriesFromData","colors","_makeColorScheme","map","pair","shape","label","_applyLayoutOptions","assigned_data","_assignTracks","every","updateSplitTrackAxis","track_data","_applyFilters","selectAll","remove","status_nodes","enter","d","getElementStatusNodeId","cliparea","Math","max","exit","data_nodes","getElementId","resolveScalableParameter","applyBehaviors","bind","render","x_min","x_max","y_min","y_max","legend_axis","tracks","track_spacing","target_height","scaleHeightToData","ticks","range","findIndex","abs","text","y_axis","axis","floor","ceiling","parent_plot","positionPanels","category_info","n_categories","unique_ids","ScaleFunctions","parent_panel","data_layer","button","setHtml","setColor","setTitle","setOnclick","toggleSplitTracks","scale_timeout","clearTimeout","setTimeout","update","use"],"mappings":"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","file":"ext/lz-intervals-track.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Interval annotation track (for chromatin state, etc). Useful for BED file data with non-overlapping intervals.\n * This is not part of the core LocusZoom library, but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~IntervalLZ}\n * * {@link module:LocusZoom_Widgets~toggle_split_tracks}\n * * {@link module:LocusZoom_ScaleFunctions~to_rgb}\n * * {@link module:LocusZoom_DataLayers~intervals}\n * * {@link module:LocusZoom_Layouts~standard_intervals}\n * * {@link module:LocusZoom_Layouts~bed_intervals_layer}\n * * {@link module:LocusZoom_Layouts~intervals_layer}\n * * {@link module:LocusZoom_Layouts~intervals}\n * * {@link module:LocusZoom_Layouts~bed_intervals}\n * * {@link module:LocusZoom_Layouts~interval_association}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import IntervalsTrack from 'locuszoom/esm/ext/lz-intervals-track';\n * LocusZoom.use(IntervalsTrack);\n * ```\n *\n * Then use the features made available by this extension. (see demos and documentation for guidance)\n * @module\n */\n\nimport * as d3 from 'd3';\n\n\n// Coordinates (start, end) are cached to facilitate rendering\nconst XCS = Symbol.for('lzXCS');\nconst YCS = Symbol.for('lzYCS');\nconst XCE = Symbol.for('lzXCE');\nconst YCE = Symbol.for('lzYCE');\n\n\nfunction install (LocusZoom) {\n const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter');\n const _Button = LocusZoom.Widgets.get('_Button');\n const _BaseWidget = LocusZoom.Widgets.get('BaseWidget');\n\n /**\n * (**extension**) Retrieve Interval Annotation Data (e.g. BED Tracks), as fetched from the LocusZoom API server (or compatible)\n * @public\n * @alias module:LocusZoom_Adapters~IntervalLZ\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n * @param {number} config.params.source The numeric ID for a specific dataset as assigned by the API server\n */\n class IntervalLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const source = this._config.source;\n const query = `?filter=id in ${source} and chromosome eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}`;\n\n const base = super._getURL(request_options);\n return `${base}${query}`;\n }\n }\n\n /**\n * (**extension**) Button to toggle split tracks mode in an intervals track. This button only works as a panel-level toolbar\n * and when used with an intervals data layer from this extension.\n * @alias module:LocusZoom_Widgets~toggle_split_tracks\n * @see module:LocusZoom_Widgets~BaseWidget\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n class ToggleSplitTracks extends _BaseWidget {\n /**\n * @param {string} layout.data_layer_id The ID of the data layer that this button is intended to control.\n */\n constructor(layout) {\n super(...arguments);\n if (!layout.data_layer_id) {\n layout.data_layer_id = 'intervals';\n }\n if (!this.parent_panel.data_layers[layout.data_layer_id]) {\n throw new Error('Toggle split tracks widget specifies an invalid data layer ID');\n }\n }\n\n update() {\n const data_layer = this.parent_panel.data_layers[this.layout.data_layer_id];\n const html = data_layer.layout.split_tracks ? 'Merge Tracks' : 'Split Tracks';\n if (this.button) {\n this.button.setHtml(html);\n this.button.show();\n this.parent.position();\n return this;\n } else {\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(html)\n .setTitle('Toggle whether tracks are split apart or merged together')\n .setOnclick(() => {\n data_layer.toggleSplitTracks();\n // FIXME: the timeout calls to scale and position (below) cause full ~5 additional re-renders\n // If we can remove these it will greatly speed up re-rendering.\n // The key problem here is that the height is apparently not known in advance and is determined after re-render.\n if (this.scale_timeout) {\n clearTimeout(this.scale_timeout);\n }\n this.scale_timeout = setTimeout(() => {\n this.parent_panel.scaleHeightToData();\n this.parent_plot.positionPanels();\n }, 0);\n this.update();\n });\n return this.update();\n }\n }\n }\n\n\n /**\n * (**extension**) Convert a value \"\"rr,gg,bb\" (if given) to a css-friendly color string: \"rgb(rr,gg,bb)\".\n * This is tailored specifically to the color specification format embraced by the BED file standard.\n * @alias module:LocusZoom_ScaleFunctions~to_rgb\n * @param {Object} parameters This function has no defined configuration options\n * @param {String|null} value The value to convert to rgb\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n function to_rgb(parameters, value) {\n return value ? `rgb(${value})` : null;\n }\n\n const default_layout = {\n start_field: 'start',\n end_field: 'end',\n track_label_field: 'state_name', // Used to label items on the y-axis\n // Used to uniquely identify tracks for coloring. This tends to lead to more stable coloring/sorting\n // than using the label field- eg, state_ids allow us to set global colors across the entire dataset,\n // not just choose unique colors within a particular narrow region. (where changing region might lead to more\n // categories and different colors)\n track_split_field: 'state_id',\n track_split_order: 'DESC',\n track_split_legend_to_y_axis: 2,\n split_tracks: true,\n track_height: 15,\n track_vertical_spacing: 3,\n bounding_box_padding: 2,\n always_hide_legend: false,\n color: '#B8B8B8',\n fill_opacity: 1,\n tooltip_positioning: 'vertical',\n };\n\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n\n /**\n * (**extension**) Implements a data layer that will render interval annotation tracks (intervals must provide start and end values)\n * Each interval (such as from a BED file) will be rendered as a rectangle. All spans can be rendered on the same\n * row, or each (auto-detected) category can be rendered as one row per category.\n *\n * This layer is intended to work with a variety of datasets with special requirements. As such, it has a lot\n * of configuration options devoted to identifying how to fill in missing information (such as color)\n *\n * @alias module:LocusZoom_DataLayers~intervals\n * @see module:LocusZoom_DataLayers~BaseDataLayer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n class LzIntervalsTrack extends BaseLayer {\n /**\n * @param {string} [layout.start_field='start'] The field that defines interval start position\n * @param {string} [layout.end_field='end'] The field that defines interval end position\n * @param {string} [layout.track_label_field='state_name'] Used to label items on the y-axis\n * @param {string} [layout.track_split_field='state_id'] Used to define categories on the y-axis. It is usually most convenient to use\n * the same value for state_field and label_field (eg 1:1 correspondence).\n * @param {*|'DESC'} [layout.track_split_order='DESC'] When in split tracks mode, should categories be shown in\n * the order given, or descending order\n * @param {number} [layout.track_split_legend_to_y_axis=2]\n * @param {boolean} [layout.split_tracks=true] Whether to show tracks as merged (one row) or split (many rows)\n * on initial render.\n * @param {number} [layout.track_height=15] The height of each interval rectangle, in px\n * @param {number} [layout.track_vertical_spacing=3]\n * @param {number} [layout.bounding_box_padding=2]\n * @param {boolean} [layout.always_hide_legend=false] Normally the legend is shown in merged mode and hidden\n * in split mode. For datasets with a very large number of categories, it may make sense to hide the legend at all times.\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#B8B8B8'] The color of each datum rectangle\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1]\n * @param {string} [layout.tooltip_positioning='vertical']\n */\n constructor(layout) {\n LocusZoom.Layouts.merge(layout, default_layout);\n super(...arguments);\n this._previous_categories = [];\n this._categories = [];\n }\n\n initialize() {\n super.initialize();\n this._statusnodes_group = this.svg.group.append('g')\n .attr('class', 'lz-data-layer-intervals lz-data-layer-intervals-statusnode');\n this._datanodes_group = this.svg.group.append('g')\n .attr('class', 'lz-data_layer-intervals');\n }\n\n /**\n * Split data into tracks such that anything with a common grouping field is in the same track\n * @param data\n * @return {unknown[]}\n * @private\n */\n _arrangeTrackSplit(data) {\n const {track_split_field} = this.layout;\n const result = {};\n data.forEach((item) => {\n const item_key = item[track_split_field];\n if (!Object.prototype.hasOwnProperty.call(result, item_key)) {\n result[item_key] = [];\n }\n result[item_key].push(item);\n });\n return result;\n }\n\n /**\n * Split data into rows using a simple greedy algorithm such that no two items overlap (share same interval)\n * Assumes that the data are sorted so item1.start always <= item2.start.\n *\n * This function can also simply return all data on a single row. This functionality may become configurable\n * in the future but for now reflects a lack of clarity in the requirements/spec. The code to split\n * overlapping items is present but may not see direct use.\n */\n _arrangeTracksLinear(data, allow_overlap = true) {\n if (allow_overlap) {\n // If overlap is allowed, then all the data can live on a single row\n return [data];\n }\n\n // ASSUMPTION: Data is given to us already sorted by start position to facilitate grouping.\n // We do not sort here because JS \"sort\" is not stable- if there are many intervals that overlap, then we\n // can get different layouts (number/order of rows) on each call to \"render\".\n //\n // At present, we decide how to update the y-axis based on whether current and former number of rows are\n // the same. An unstable sort leads to layout thrashing/too many re-renders. FIXME: don't rely on counts\n const {start_field, end_field} = this.layout;\n\n const grouped_data = [[]]; // Prevent two items from colliding by rendering them to different rows, like genes\n data.forEach((item, index) => {\n for (let i = 0; i < grouped_data.length; i++) {\n // Iterate over all rows of the\n const row_to_test = grouped_data[i];\n const last_item = row_to_test[row_to_test.length - 1];\n // Some programs report open intervals, eg 0-1,1-2,2-3; these points are not considered to overlap (hence the test isn't \"<=\")\n const has_overlap = last_item && (item[start_field] < last_item[end_field]) && (last_item[start_field] < item[end_field]);\n if (!has_overlap) {\n // If there is no overlap, add item to current row, and move on to the next item\n row_to_test.push(item);\n return;\n }\n }\n // If this item would collide on all existing rows, create a new row\n grouped_data.push([item]);\n });\n return grouped_data;\n }\n\n /**\n * Annotate each item with the track number, and return.\n * @param {Object[]}data\n * @private\n * @return [String[], Object[]] Return the categories and the data array\n */\n _assignTracks(data) {\n // Flatten the grouped data.\n const {x_scale} = this.parent;\n const {start_field, end_field, bounding_box_padding, track_height} = this.layout;\n\n const grouped_data = this.layout.split_tracks ? this._arrangeTrackSplit(data) : this._arrangeTracksLinear(data, true);\n const categories = Object.keys(grouped_data);\n if (this.layout.track_split_order === 'DESC') {\n categories.reverse();\n }\n\n categories.forEach((key, row_index) => {\n const row = grouped_data[key];\n row.forEach((item) => {\n item[XCS] = x_scale(item[start_field]);\n item[XCE] = x_scale(item[end_field]);\n item[YCS] = row_index * this.getTrackHeight() + bounding_box_padding;\n item[YCE] = item[YCS] + track_height;\n // Store the row ID, so that clicking on a point can find the right status node (big highlight box)\n item.track = row_index;\n });\n });\n // We're mutating elements of the original data array as a side effect: the return value here is\n // interchangeable with `this.data` for subsequent usages\n // TODO: Can replace this with array.flat once polyfill support improves\n return [categories, Object.values(grouped_data).reduce((acc, val) => acc.concat(val), [])];\n }\n\n /**\n * When we are in \"split tracks mode\", it's convenient to wrap all individual annotations with a shared\n * highlight box that wraps everything on that row.\n *\n * This is done automatically by the \"setElementStatus\" code, if this function returns a non-null value\n *\n * To define shared highlighting on the track split field define the status node id override\n * to generate an ID common to the track when we're actively splitting data out to separate tracks\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n if (this.layout.split_tracks) {\n // Data nodes are bound to data objects, but the \"status_nodes\" selection is bound to numeric row IDs\n const track = typeof element === 'object' ? element.track : element;\n const base = `${this.getBaseId()}-statusnode-${track}`;\n return base.replace(/[^\\w]/g, '_');\n }\n // In merged tracks mode, there is no separate status node\n return null;\n }\n\n // Helper function to sum layout values to derive total height for a single interval track\n getTrackHeight() {\n return this.layout.track_height\n + this.layout.track_vertical_spacing\n + (2 * this.layout.bounding_box_padding);\n }\n\n // Modify the layout as necessary to ensure that appropriate color, label, and legend options are available\n // Even when not displayed, the legend is used to generate the y-axis ticks\n _applyLayoutOptions() {\n const self = this;\n const base_layout = this._base_layout;\n const render_layout = this.layout;\n const base_color_scale = base_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'categorical_bin';\n });\n const color_scale = render_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'categorical_bin';\n });\n if (!base_color_scale) {\n // This can be a placeholder (empty categories & values), but it needs to be there\n throw new Error('Interval tracks must define a `categorical_bin` color scale');\n }\n\n const has_colors = base_color_scale.parameters.categories.length && base_color_scale.parameters.values.length;\n const has_legend = base_layout.legend && base_layout.legend.length;\n\n if (!!has_colors ^ !!has_legend) {\n // Don't allow color OR legend to be set manually. It must be both, or neither.\n throw new Error('To use a manually specified color scheme, both color and legend options must be set.');\n }\n\n // Harvest any information about an explicit color field that should be considered when generating colors\n const rgb_option = base_layout.color.find(function (item) {\n return item.scale_function && item.scale_function === 'to_rgb';\n });\n const rgb_field = rgb_option && rgb_option.field;\n\n // Auto-generate legend based on data\n const known_categories = this._generateCategoriesFromData(this.data, rgb_field); // [id, label, itemRgb] items\n\n if (!has_colors && !has_legend) {\n // If no color scheme pre-defined, then make a color scheme that is appropriate and apply to the plot\n // The legend must match the color scheme. If we generate one, then we must generate both.\n\n const colors = this._makeColorScheme(known_categories);\n color_scale.parameters.categories = known_categories.map(function (item) {\n return item[0];\n });\n color_scale.parameters.values = colors;\n\n this.layout.legend = known_categories.map(function (pair, index) {\n const id = pair[0];\n const label = pair[1];\n const item_color = color_scale.parameters.values[index];\n const item = { shape: 'rect', width: 9, label: label, color: item_color };\n item[self.layout.track_split_field] = id;\n return item;\n });\n }\n }\n\n // Implement the main render function\n render() {\n //// Autogenerate layout options if not provided\n this._applyLayoutOptions();\n\n // Determine the appropriate layout for tracks. Store the previous categories (y axis ticks) to decide\n // whether the axis needs to be re-rendered.\n this._previous_categories = this._categories;\n const [categories, assigned_data] = this._assignTracks(this.data);\n this._categories = categories;\n // Update the legend axis if the number of ticks changed\n const labels_changed = !categories.every( (item, index) => item === this._previous_categories[index]);\n if (labels_changed) {\n this.updateSplitTrackAxis(categories);\n return;\n }\n\n // Apply filters to only render a specified set of points. Hidden fields will still be given space to render, but not shown.\n const track_data = this._applyFilters(assigned_data);\n\n // Clear before every render so that, eg, highlighting doesn't persist if we load a region with different\n // categories (row 2 might be a different category and it's confusing if the row stays highlighted but changes meaning)\n // Highlighting will automatically get added back if it actually makes sense, courtesy of setElementStatus,\n // if a selected item is still in view after the new region loads.\n this._statusnodes_group.selectAll('rect')\n .remove();\n\n // Reselect in order to add new data\n const status_nodes = this._statusnodes_group.selectAll('rect')\n .data(d3.range(categories.length));\n\n if (this.layout.split_tracks) {\n // Status nodes: a big highlight box around all items of the same type. Used in split tracks mode,\n // because everything on the same row is the same category and a group makes sense\n // There are no status nodes in merged mode, because the same row contains many kinds of things\n\n // Status nodes are 1 per row, so \"data\" can just be a dummy list of possible row IDs\n // Each status node is a box that runs the length of the panel and receives a special \"colored box\" css\n // style when selected\n const height = this.getTrackHeight();\n status_nodes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared')\n .attr('rx', this.layout.bounding_box_padding)\n .attr('ry', this.layout.bounding_box_padding)\n .merge(status_nodes)\n .attr('id', (d) => this.getElementStatusNodeId(d))\n .attr('x', 0)\n .attr('y', (d) => (d * height))\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', Math.max(height - this.layout.track_vertical_spacing, 1));\n }\n status_nodes.exit()\n .remove();\n\n // Draw rectangles for the data (intervals)\n const data_nodes = this._datanodes_group.selectAll('rect')\n .data(track_data, (d) => d[this.layout.id_field]);\n\n data_nodes.enter()\n .append('rect')\n .merge(data_nodes)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => d[XCS])\n .attr('y', (d) => d[YCS])\n .attr('width', (d) => Math.max(d[XCE] - d[XCS], 1))\n .attr('height', this.layout.track_height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n data_nodes.exit()\n .remove();\n\n this._datanodes_group\n .call(this.applyBehaviors.bind(this));\n\n // The intervals track allows legends to be dynamically generated, in which case space can only be\n // allocated after the panel has been rendered.\n if (this.parent && this.parent.legend) {\n this.parent.legend.render();\n }\n }\n\n _getTooltipPosition(tooltip) {\n return {\n x_min: tooltip.data[XCS],\n x_max: tooltip.data[XCE],\n y_min: tooltip.data[YCS],\n y_max: tooltip.data[YCE],\n };\n }\n\n // Redraw split track axis or hide it, and show/hide the legend, as determined\n // by current layout parameters and data\n updateSplitTrackAxis(categories) {\n const legend_axis = this.layout.track_split_legend_to_y_axis ? `y${this.layout.track_split_legend_to_y_axis}` : false;\n if (this.layout.split_tracks) {\n const tracks = +categories.length || 0;\n const track_height = +this.layout.track_height || 0;\n const track_spacing = 2 * (+this.layout.bounding_box_padding || 0) + (+this.layout.track_vertical_spacing || 0);\n const target_height = (tracks * track_height) + ((tracks - 1) * track_spacing);\n this.parent.scaleHeightToData(target_height);\n if (legend_axis && this.parent.legend) {\n this.parent.legend.hide();\n this.parent.layout.axes[legend_axis] = {\n render: true,\n ticks: [],\n range: {\n start: (target_height - (this.layout.track_height / 2)),\n end: (this.layout.track_height / 2),\n },\n };\n // There is a very tight coupling between the display directives: each legend item must identify a key\n // field for unique tracks. (Typically this is `state_id`, the same key field used to assign unique colors)\n // The list of unique keys corresponds to the order along the y-axis\n this.layout.legend.forEach((element) => {\n const key = element[this.layout.track_split_field];\n let track = categories.findIndex((item) => item === key);\n if (track !== -1) {\n if (this.layout.track_split_order === 'DESC') {\n track = Math.abs(track - tracks - 1);\n }\n this.parent.layout.axes[legend_axis].ticks.push({\n y: track - 1,\n text: element.label,\n });\n }\n });\n this.layout.y_axis = {\n axis: this.layout.track_split_legend_to_y_axis,\n floor: 1,\n ceiling: tracks,\n };\n }\n // This will trigger a re-render\n this.parent_plot.positionPanels();\n } else {\n if (legend_axis && this.parent.legend) {\n if (!this.layout.always_hide_legend) {\n this.parent.legend.show();\n }\n this.parent.layout.axes[legend_axis] = { render: false };\n this.parent.render();\n }\n }\n return this;\n }\n\n // Method to not only toggle the split tracks boolean but also update\n // necessary display values to animate a complete merge/split\n toggleSplitTracks() {\n this.layout.split_tracks = !this.layout.split_tracks;\n if (this.parent.legend && !this.layout.always_hide_legend) {\n this.parent.layout.margin.bottom = 5 + (this.layout.split_tracks ? 0 : this.parent.legend.layout.height + 5);\n }\n this.render();\n return this;\n }\n\n // Choose an appropriate color scheme based on the number of items in the track, and whether or not we are\n // using explicitly provided itemRgb information\n _makeColorScheme(category_info) {\n // If at least one element has an explicit itemRgb, assume the entire dataset has colors. BED intervals require rgb triplets,so assume that colors will always be \"r,g,b\" format.\n const has_explicit_colors = category_info.find((item) => item[2]);\n if (has_explicit_colors) {\n return category_info.map((item) => to_rgb({}, item[2]));\n }\n\n // Use a set of color schemes for common 15, 18, or 25 state models, as specified from:\n // https://egg2.wustl.edu/roadmap/web_portal/chr_state_learning.html\n // These are actually reversed so that dim colors come first, on the premise that usually these are the\n // most common states\n const n_categories = category_info.length;\n if (n_categories <= 15) {\n return ['rgb(212,212,212)', 'rgb(192,192,192)', 'rgb(128,128,128)', 'rgb(189,183,107)', 'rgb(233,150,122)', 'rgb(205,92,92)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,0)', 'rgb(194,225,5)', 'rgb(0,100,0)', 'rgb(0,128,0)', 'rgb(50,205,50)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n } else if (n_categories <= 18) {\n return ['rgb(212,212,212)', 'rgb(192,192,192)', 'rgb(128,128,128)', 'rgb(189,183,107)', 'rgb(205,92,92)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,0)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(0,100,0)', 'rgb(0,128,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n } else {\n // If there are more than 25 categories, the interval layer will fall back to the 'null value' option\n return ['rgb(212,212,212)', 'rgb(128,128,128)', 'rgb(112,48,160)', 'rgb(230,184,183)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,102)', 'rgb(255,255,0)', 'rgb(255,255,0)', 'rgb(255,255,0)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(255,195,77)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(194,225,5)', 'rgb(0,150,0)', 'rgb(0,128,0)', 'rgb(0,128,0)', 'rgb(0,128,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,69,0)', 'rgb(255,0,0)'];\n }\n }\n\n /**\n * Find all of the unique tracks (a combination of name and ID information)\n * @param {Object} data\n * @param {String} [rgb_field] A field that contains an RGB value. Aimed at BED files with an itemRgb column\n * @private\n * @returns {Array} All [unique_id, label, color] pairs in data. The unique_id is the thing used to define groupings\n * most unambiguously.\n */\n _generateCategoriesFromData(data, rgb_field) {\n const self = this;\n // Use the hard-coded legend if available (ignoring any mods on re-render)\n const legend = this._base_layout.legend;\n if (legend && legend.length) {\n return legend.map((item) => [item[this.layout.track_split_field], item.label, item.color]);\n }\n\n // Generate options from data, if no preset legend exists\n const unique_ids = {}; // make categories unique\n const categories = [];\n\n data.forEach((item) => {\n const id = item[self.layout.track_split_field];\n if (!Object.prototype.hasOwnProperty.call(unique_ids, id)) {\n unique_ids[id] = null;\n // If rgbfield is null, then the last entry is undefined/null as well\n categories.push([id, item[this.layout.track_label_field], item[rgb_field]]);\n }\n });\n return categories;\n }\n }\n\n /**\n * (**extension**) A basic tooltip with information to be shown over an intervals datum\n * @alias module:LocusZoom_Layouts~standard_intervals\n * @type tooltip\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_tooltip_layout = {\n closable: false,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{intervals:state_name|htmlescape}}
    {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}',\n };\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for chromHMM output,\n * in which various states are assigned numeric state IDs and (<= as many) text state names.\n *\n * This layout is deprecated; most usages would be better served by the bed_intervals_layer layout instead.\n * @alias module:LocusZoom_Layouts~intervals_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_layer_layout = {\n namespace: { 'intervals': 'intervals' },\n id: 'intervals',\n type: 'intervals',\n tag: 'intervals',\n id_field: '{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}',\n start_field: 'intervals:start',\n end_field: 'intervals:end',\n track_split_field: 'intervals:state_name',\n track_label_field: 'intervals:state_name',\n split_tracks: false,\n always_hide_legend: true,\n color: [\n {\n // If present, an explicit color field will override any other option (and be used to auto-generate legend)\n field: 'intervals:itemRgb',\n scale_function: 'to_rgb',\n },\n {\n // TODO: Consider changing this to stable_choice in the future, for more stable coloring\n field: 'intervals:state_name',\n scale_function: 'categorical_bin',\n parameters: {\n // Placeholder. Empty categories and values will automatically be filled in when new data loads.\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n },\n ],\n legend: [], // Placeholder; auto-filled when data loads.\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: intervals_tooltip_layout,\n };\n\n\n /**\n * (**extension**) A data layer with some preconfigured options for intervals display. This example was designed for standard BED3+ files and the field names emitted by the LzParsers extension.\n * @alias module:LocusZoom_Layouts~bed_intervals_layer\n * @type data_layer\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const bed_intervals_layer_layout = LocusZoom.Layouts.merge({\n id_field: '{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}',\n start_field: 'intervals:chromStart',\n end_field: 'intervals:chromEnd',\n track_split_field: 'intervals:name',\n track_label_field: 'intervals:name',\n split_tracks: true,\n always_hide_legend: false,\n color: [\n {\n // If present, an explicit color field will override any other option (and be used to auto-generate legend)\n field: 'intervals:itemRgb',\n scale_function: 'to_rgb',\n },\n {\n // TODO: Consider changing this to stable_choice in the future, for more stable coloring\n field: 'intervals:name',\n scale_function: 'categorical_bin',\n parameters: {\n // Placeholder. Empty categories and values will automatically be filled in when new data loads.\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n },\n ],\n tooltip: LocusZoom.Layouts.merge({\n html: `Group: {{intervals:name|htmlescape}}
    \nRegion: {{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}}\n{{#if intervals:score}}
    \nScore: {{intervals:score|htmlescape}}{{/if}}`,\n }, intervals_tooltip_layout),\n }, intervals_layer_layout);\n\n\n /**\n * (**extension**) A panel containing an intervals data layer, eg for BED tracks. This is a legacy layout whose field names were specific to one partner site.\n * @alias module:LocusZoom_Layouts~intervals\n * @type panel\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_panel_layout = {\n id: 'intervals',\n tag: 'intervals',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 150, bottom: 5, left: 70 },\n toolbar: (function () {\n const l = LocusZoom.Layouts.get('toolbar', 'standard_panel');\n l.widgets.push({\n type: 'toggle_split_tracks',\n data_layer_id: 'intervals',\n position: 'right',\n });\n return l;\n })(),\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n legend: {\n hidden: true,\n orientation: 'horizontal',\n origin: { x: 50, y: 0 },\n pad_from_bottom: 5,\n },\n data_layers: [intervals_layer_layout],\n };\n\n /**\n * (**extension**) A panel containing an intervals data layer, eg for BED tracks. These field names match those returned by the LzParsers extension.\n * @alias module:LocusZoom_Layouts~bed_intervals\n * @type panel\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const bed_intervals_panel_layout = LocusZoom.Layouts.merge({\n // Normal BED tracks show the panel legend in collapsed mode!\n min_height: 120,\n height: 120,\n data_layers: [bed_intervals_layer_layout],\n }, intervals_panel_layout);\n\n /**\n * (**extension**) A plot layout that shows association summary statistics, genes, and interval data. This example assumes\n * chromHMM data. (see panel layout) Few people will use the full intervals plot layout directly outside of an example.\n * @alias module:LocusZoom_Layouts~interval_association\n * @type plot\n * @see {@link module:ext/lz-intervals-track} for required extension and installation instructions\n */\n const intervals_plot_layout = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n LocusZoom.Layouts.get('panel', 'association'),\n LocusZoom.Layouts.merge({ min_height: 120, height: 120 }, intervals_panel_layout),\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n\n LocusZoom.Adapters.add('IntervalLZ', IntervalLZ);\n LocusZoom.DataLayers.add('intervals', LzIntervalsTrack);\n\n LocusZoom.Layouts.add('tooltip', 'standard_intervals', intervals_tooltip_layout);\n LocusZoom.Layouts.add('data_layer', 'intervals', intervals_layer_layout);\n LocusZoom.Layouts.add('data_layer', 'bed_intervals', bed_intervals_layer_layout);\n LocusZoom.Layouts.add('panel', 'intervals', intervals_panel_layout);\n LocusZoom.Layouts.add('panel', 'bed_intervals', bed_intervals_panel_layout);\n LocusZoom.Layouts.add('plot', 'interval_association', intervals_plot_layout);\n\n LocusZoom.ScaleFunctions.add('to_rgb', to_rgb);\n\n LocusZoom.Widgets.add('toggle_split_tracks', ToggleSplitTracks);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-parsers.min.js b/dist/ext/lz-parsers.min.js index 74b44fb1..8f31a7e3 100644 --- a/dist/ext/lz-parsers.min.js +++ b/dist/ext/lz-parsers.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.1 */ +/*! Locuszoom 0.14.0-beta.2 */ var LzParsers;(()=>{"use strict";var e={d:(r,t)=>{for(var l in t)e.o(t,l)&&!e.o(r,l)&&Object.defineProperty(r,l,{enumerable:!0,get:t[l]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r)},r={};e.d(r,{default:()=>d});const t=new Set(["",".","NA","N/A","n/a","nan","-nan","NaN","-NaN","null","NULL","None",null]),l=/([\d.-]+)([\sxeE]*)([0-9-]*)/;function n(e){return Number.isInteger(e)}function o(e,r=t,l=null){return e.map((e=>r.has(e)?l:e))}function a(e){return e.replace(/^chr/g,"").toUpperCase()}function s(e,r=!1){if(null===e)return e;const t=+e;if(r)return t;if(t<0||t>1)throw new Error("p value is not in the allowed range");if(0===t){if("0"===e)return 1/0;let[,r,,t]=e.match(l);return r=+r,t=""!==t?+t:0,0===r?1/0:-(Math.log10(+r)+ +t)}return-Math.log10(t)}function u(e){return null==e||"."===e?null:e}function i(e){return(e=u(e))?+e:null}const c=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function f(e,r=!1){const t=e&&e.match(c);if(t)return t.slice(1);if(r)return null;throw new Error(`Could not understand marker format for ${e}. Should be of format chr:pos or chr:pos_ref/alt`)}function p(e){const r=f(e);if(!r)throw new Error(`Unable to normalize marker format for variant: ${e}`);const[t,l,n,o]=r;let a=`${t}:${l}`;return n&&o&&(a+=`_${n}/${o}`),a}function _(e,r){const t=Array(r.length+1).fill(null).map((()=>Array(e.length+1).fill(null)));for(let r=0;r<=e.length;r+=1)t[0][r]=r;for(let e=0;e<=r.length;e+=1)t[e][0]=e;for(let l=1;l<=r.length;l+=1)for(let n=1;n<=e.length;n+=1){const o=e[n-1]===r[l-1]?0:1;t[l][n]=Math.min(t[l][n-1]+1,t[l-1][n]+1,t[l-1][n-1]+o)}return t[r.length][e.length]}function m(e,r,t=2){let l=t+1,n=null;for(let t=0;t_(o,e))));ae.variant1===r.ld_refvar))}}e.Adapters.add("UserTabixLD",l)}}"undefined"!=typeof LocusZoom&&LocusZoom.use(h);const d={install:h,makeBed12Parser:function({normalize:e=!0}={}){return r=>{const t=r.trim().split("\t");let[l,n,o,s,c,f,p,_,m,h,d,v]=t;if(!(l&&n&&o))throw new Error("Sample data must provide all required BED columns");if(f=u(f),e&&(l=a(l),n=+n+1,o=+o,c=i(c),p=i(p),_=i(_),m=u(m),h=i(h),d=u(d),d=d?d.replace(/,$/,"").split(",").map((e=>+e)):null,v=u(v),v=v?v.replace(/,$/,"").split(",").map((e=>+e+1)):null,d&&v&&h&&(d.length!==h||v.length!==h)))throw new Error("Block size and start information should provide the same number of items as in blockCount");return{chrom:l,chromStart:n,chromEnd:o,name:s,score:c,strand:f,thickStart:p,thickEnd:_,itemRgb:m,blockCount:h,blockSizes:d,blockStarts:v}}},makeGWASParser:function({marker_col:e,chrom_col:r,pos_col:l,ref_col:o,alt_col:u,pvalue_col:i,is_neg_log_pvalue:c=!1,rsid_col:p,beta_col:_,stderr_beta_col:m,allele_freq_col:h,allele_count_col:d,n_samples_col:v,is_alt_effect:g=!0,delimiter:w="\t"}){if(n(e)&&n(r)&&n(l))throw new Error("Must specify either marker OR chr + pos");if(!(n(e)||n(r)&&n(l)))throw new Error("Must specify how to locate marker");if(n(d)&&n(h))throw new Error("Allele count and frequency options are mutually exclusive");if(n(d)&&!n(v))throw new Error("To calculate allele frequency from counts, you must also provide n_samples");return b=>{const k=b.split(w);let y,E,q,A,N,$,S,L=null,z=null,P=null,C=null;if(n(e))[y,E,q,A]=f(k[e-1],!1);else{if(!n(r)||!n(l))throw new Error("Must specify all fields required to identify the variant");y=k[r-1],E=k[l-1]}if(y=a(y),y.startsWith("RS"))throw new Error(`Invalid chromosome specified: value "${y}" is an rsID`);n(o)&&(q=k[o-1]),n(u)&&(A=k[u-1]),n(p)&&(L=k[p-1]),t.has(q)&&(q=null),t.has(A)&&(A=null),t.has(L)?L=null:L&&(L=L.toLowerCase(),L.startsWith("rs")||(L=`rs${L}`));const O=s(k[i-1],c);q=q||null,A=A||null,n(h)&&(N=k[h-1]),n(d)&&($=k[d-1],S=k[v-1]),n(_)&&(z=k[_-1],z=t.has(z)?null:+z),n(m)&&(P=k[m-1],P=t.has(P)?null:+P),(h||d)&&(C=function({freq:e,allele_count:r,n_samples:l,is_alt_effect:n=!0}){if(void 0!==e&&void 0!==r)throw new Error("Frequency and allele count options are mutually exclusive");let o;if(void 0===e&&(t.has(r)||t.has(l)))return null;if(void 0===e&&void 0!==r)o=+r/+l/2;else{if(t.has(e))return null;o=+e}if(o<0||o>1)throw new Error("Allele frequency is not in the allowed range");return n?o:1-o}({freq:N,allele_count:$,n_samples:S,is_alt_effect:g}));const R=q&&A?`_${q}/${A}`:"";return{chromosome:y,position:+E,ref_allele:q?q.toUpperCase():null,alt_allele:A?A.toUpperCase():null,variant:`${y}:${E}${R}`,rsid:L,log_pvalue:O,beta:z,stderr_beta:P,alt_allele_freq:C}}},guessGWAS:function(e,r,t=1){const l=e.map((e=>e?e.toLowerCase():e));l[0].replace("/^#+/","");const n=function(e,r){let t;const l=(e,r,l)=>{const n=o(r.map((r=>r[e])));try{t=n.map((e=>s(e,l)))}catch(e){return!1}return t.every((e=>!Number.isNaN(e)))},n=m(["neg_log_pvalue","log_pvalue","log_pval","logpvalue"],e),a=m(["pvalue","p.value","p-value","pval","p_score","p","p_value"],e);return null!==n&&l(n,r,!0)?{pvalue_col:n+1,is_neg_log_pvalue:!0}:a&&l(a,r,!1)?{pvalue_col:a+1,is_neg_log_pvalue:!1}:null}(l,r);if(!n)return null;l[n.pvalue_col-1]=null;const a=function(e,r){const t=r[0];let l=m(["snpid","marker","markerid","snpmarker","chr:position"],e);if(null!==l&&f(t[l],!0))return l+=1,{marker_col:l};const n=e.slice(),o=[["chrom_col",["chrom","chr","chromosome"],!0],["pos_col",["position","pos","begin","beg","bp","end","ps","base_pair_location"],!0],["ref_col",["A1","ref","reference","allele0","allele1"],!1],["alt_col",["A2","alt","alternate","allele1","allele2"],!1]],a={};for(let e=0;e{l[a[e]]=null}));const u=function(e,r){function t(e,r){const t=o(r.map((r=>r[e])));let l;try{l=t.filter((e=>null!==e)).map((e=>+e))}catch(e){return!1}return l.every((e=>!Number.isNaN(e)))}const l=m(["beta","effect_size","alt_effsize","effect"],e,0),n=m(["stderr_beta","stderr","sebeta","effect_size_sd","se","standard_error"],e,0),a={};return null!==l&&t(l,r)&&(a.beta_col=l+1),null!==n&&t(n,r)&&(a.stderr_beta_col=n+1),a}(l,r);return n&&a?Object.assign({},n,a,u||{}):null},makePlinkLdParser:function({normalize:e=!0}={}){return r=>{let[t,l,n,o,s,u,i]=r.trim().split("\t");return e&&(t=a(t),o=a(o),n=p(n),u=p(u),l=+l,s=+s,i=+i),{chromosome1:t,position1:l,variant1:n,chromosome2:o,position2:s,variant2:u,correlation:i}}}};LzParsers=r.default})(); //# sourceMappingURL=lz-parsers.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-tabix-source.min.js b/dist/ext/lz-tabix-source.min.js index a6478c12..d5a9c374 100644 --- a/dist/ext/lz-tabix-source.min.js +++ b/dist/ext/lz-tabix-source.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.1 */ +/*! Locuszoom 0.14.0-beta.2 */ var LzTabix;(()=>{var t={398:t=>{var i=15,e=-2,n=-3,r=-5,s=13,a=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],o=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],h=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],l=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],f=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],u=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],d=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];function _(){}function c(){this.was=[0]}_.prototype.inflateInit=function(t,i){return t||(t=15),i&&(i=!1),this.istate=new c,this.istate.inflateInit(this,i?-t:t)},_.prototype.inflate=function(t){return null==this.istate?e:this.istate.inflate(this,t)},_.prototype.inflateEnd=function(){if(null==this.istate)return e;var t=istate.inflateEnd(this);return this.istate=null,t},_.prototype.inflateSync=function(){return istate.inflateSync(this)},_.prototype.inflateSetDictionary=function(t,i){return istate.inflateSetDictionary(this,t,i)},c.prototype.inflateReset=function(t){return null==t||null==t.istate?e:(t.total_in=t.total_out=0,t.msg=null,t.istate.mode=0!=t.istate.nowrap?7:0,t.istate.blocks.reset(t,null),0)},c.prototype.inflateEnd=function(t){return null!=this.blocks&&this.blocks.free(t),this.blocks=null,0},c.prototype.inflateInit=function(t,i){return t.msg=null,this.blocks=null,nowrap=0,i<0&&(i=-i,nowrap=1),i<8||i>15?(this.inflateEnd(t),e):(this.wbits=i,t.istate.blocks=new v(t,0!=t.istate.nowrap?null:this,1<>4)>t.istate.wbits){t.istate.mode=s,t.msg="invalid window size",t.istate.marker=5;break}t.istate.mode=1;case 1:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,o=255&t.next_in[t.next_in_index++],((t.istate.method<<8)+o)%31!=0){t.istate.mode=s,t.msg="incorrect header check",t.istate.marker=5;break}if(0==(32&o)){t.istate.mode=7;break}t.istate.mode=2;case 2:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=3;case 3:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=4;case 4:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=5;case 5:return 0==t.avail_in?a:(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.adler=t.istate.need,t.istate.mode=6,2);case 6:return t.istate.mode=s,t.msg="need dictionary",t.istate.marker=0,e;case 7:if((a=t.istate.blocks.proc(t,a))==n){t.istate.mode=s,t.istate.marker=0;break}if(0==a&&(a=i),1!=a)return a;if(a=i,t.istate.blocks.reset(t,t.istate.was),0!=t.istate.nowrap){t.istate.mode=12;break}t.istate.mode=8;case 8:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=9;case 9:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=10;case 10:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=11;case 11:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.istate.was[0]!=t.istate.need){t.istate.mode=s,t.msg="incorrect data check",t.istate.marker=5;break}t.istate.mode=12;case 12:return 1;case s:return n;default:return e}},c.prototype.inflateSetDictionary=function(t,i,r){var s=0,a=r;return null==t||null==t.istate||6!=t.istate.mode?e:t._adler.adler32(1,i,0,r)!=t.adler?n:(t.adler=t._adler.adler32(0,null,0,0),a>=1<>>1){case 0:o>>>=3,o>>>=r=7&(h-=3),h-=r,this.mode=1;break;case 1:w(v=new Int32Array(1),p=new Int32Array(1),m=[],y=[],t),this.codes.init(v[0],p[0],m[0],0,y[0],0,t),o>>>=3,h-=3,this.mode=6;break;case 2:o>>>=3,h-=3,this.mode=3;break;case 3:return o>>>=3,h-=3,this.mode=s,t.msg="invalid block type",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i)}break;case 1:for(;h<32;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>16&65535)!=(65535&o))return this.mode=s,t.msg="invalid stored block lengths",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.left=65535&o,o=h=0,this.mode=0!=this.left?2:0!=this.last?7:0;break;case 2:if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);if(0==d&&(u==end&&0!=read&&(d=(u=0)f&&(r=f),r>d&&(r=d),g(t.next_in,l,this.window,u,r),l+=r,f-=r,u+=r,d-=r,0!=(this.left-=r))break;this.mode=0!=this.last?7:0;break;case 3:for(;h<14;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<29||(r>>5&31)>29)return this.mode=9,t.msg="too many length or distance symbols",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);if(r=258+(31&r)+(r>>5&31),null==this.blens||this.blens.length>>=14,h-=14,this.index=0,mode=4;case 4:for(;this.index<4+(this.table>>>10);){for(;h<3;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>=3,h-=3}for(;this.index<19;)this.blens[b[this.index++]]=0;if(this.bb[0]=7,0!=(r=this.inftree.inflate_trees_bits(this.blens,this.bb,this.tb,this.hufts,t)))return(i=r)==n&&(this.blens=null,this.mode=9),this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);this.index=0,this.mode=5;case 5:for(;r=this.table,this.index<258+(31&r)+(r>>5&31);){var c,x;for(r=this.bb[0];h>>=r,h-=r,this.blens[this.index++]=x;else{for(_=18==x?7:x-14,c=18==x?11:3;h>>=r)&a[_],o>>>=_,h-=_,(_=this.index)+c>258+(31&(r=this.table))+(r>>5&31)||16==x&&_<1)return this.blens=null,this.mode=9,t.msg="invalid bit length repeat",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);x=16==x?this.blens[_-1]:0;do{this.blens[_++]=x}while(0!=--c);this.index=_}}this.tb[0]=-1;var v=new Int32Array(1),p=new Int32Array(1),m=new Int32Array(1),y=new Int32Array(1);if(v[0]=9,p[0]=6,r=this.table,0!=(r=this.inftree.inflate_trees_dynamic(257+(31&r),1+(r>>5&31),this.blens,v,p,m,y,this.hufts,t)))return r==n&&(this.blens=null,this.mode=s),i=r,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.codes.init(v[0],p[0],this.hufts,m[0],this.hufts,y[0],t),this.mode=6;case 6:if(this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,1!=(i=this.codes.proc(this,t,i)))return this.inflate_flush(t,i);if(i=0,this.codes.free(t),l=t.next_in_index,f=t.avail_in,o=this.bitb,h=this.bitk,d=(u=this.write)t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,(s+=e)==this.end&&(s=0,this.write==this.end&&(this.write=0),(e=this.write-s)>t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,s+=e),t.next_out_index=n,this.read=s,i};function p(){}function m(){}function w(t,i,e,n,r){return t[0]=9,i[0]=5,e[0]=o,n[0]=h,0}p.prototype.init=function(t,i,e,n,r,s,a){this.mode=0,this.lbits=t,this.dbits=i,this.ltree=e,this.ltree_index=n,this.dtree=r,this.dtree_index=s,this.tree=null},p.prototype.proc=function(t,i,r){var s,o,h,l,f,u,d,_=0,c=0,x=0;for(x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)=258&&l>=10&&(t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,r=this.inflate_fast(this.lbits,this.dbits,this.ltree,this.ltree_index,this.dtree,this.dtree_index,t,i),x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)>>=this.tree[o+1],c-=this.tree[o+1],0==(h=this.tree[o])){this.lit=this.tree[o+2],this.mode=6;break}if(0!=(16&h)){this.get=15&h,this.len=this.tree[o+2],this.mode=2;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}if(0!=(32&h)){this.mode=7;break}return this.mode=9,i.msg="invalid literal/length code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 2:for(s=this.get;c>=s,c-=s,this.need=this.dbits,this.tree=this.dtree,this.tree_index=this.dtree_index,this.mode=3;case 3:for(s=this.need;c>=this.tree[o+1],c-=this.tree[o+1],0!=(16&(h=this.tree[o]))){this.get=15&h,this.dist=this.tree[o+2],this.mode=4;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}return this.mode=9,i.msg="invalid distance code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 4:for(s=this.get;c>=s,c-=s,this.mode=5;case 5:for(d=f-this.dist;d<0;)d+=t.end;for(;0!=this.len;){if(0==u&&(f==t.end&&0!=t.read&&(u=(f=0)7&&(c-=8,l++,x--),t.write=f,r=t.inflate_flush(i,r),u=(f=t.write)>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15,k=u[S+2]+(c&a[_]),c>>=_,x-=_;x<15;)v--,c|=(255&l.next_in[b++])<>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15;x<_;)v--,c|=(255&l.next_in[b++])<>=_,x-=_,m-=k,p>=A)C=p-A,h.window[p++]=h.window[C++],h.window[p++]=h.window[C++],k-=2;else{C=p-A;do{C+=h.end}while(C<0);if(k>(_=h.end-C)){if(k-=_,p-C>0&&_>p-C)do{h.window[p++]=h.window[C++]}while(0!=--_);else g(h.window,C,h.window,p,_),p+=_,C+=_,_=0;C=0}}do{h.window[p++]=h.window[C++]}while(0!=--k);break}if(0!=(64&_))return l.msg="invalid distance code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n;f+=u[S+2],_=u[S=3*(d+(f+=c&a[_]))]}break}if(0!=(64&_))return 0!=(32&_)?(v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,1):(l.msg="invalid literal/length code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n);if(f+=u[S+2],0==(_=u[S=3*(d+(f+=c&a[_]))])){c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--;break}}else c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--}while(m>=258&&v>=10);return v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,0},m.prototype.huft_build=function(t,e,s,a,o,h,l,f,u,d,_){var c,x,b,v,p,m,w,y,k,A,C,S,R,I,L;A=0,p=s;do{this.c[t[e+A]]++,A++,p--}while(0!=p);if(this.c[0]==s)return l[0]=-1,f[0]=0,0;for(y=f[0],m=1;m<=i&&0==this.c[m];m++);for(w=m,yp&&(y=p),f[0]=y,I=1<S+y;){if(v++,L=(L=b-(S+=y))>y?y:L,(x=1<<(m=w-S))>c+1&&(x-=c+1,R=w,m1440)return n;this.u[v]=C=this.hn[0],this.hn[0]+=L,0!=v?(this.x[v]=p,this.r[0]=m,this.r[1]=y,m=p>>>S-y,this.r[2]=C-this.u[v-1]-m,g(this.r,0,u,3*(this.u[v-1]+m),3)):l[0]=C}for(this.r[1]=w-S,A>=s?this.r[0]=192:_[A]>>S;m>>=1)p^=m;for(p^=m,k=(1<257?(x==n?c.msg="oversubscribed distance tree":x==r?(c.msg="incomplete distance tree",x=n):-4!=x&&(c.msg="empty distance tree with lengths",x=n),x):0)},m.prototype.initWorkArea=function(t){null==this.hn&&(this.hn=new Int32Array(1),this.v=new Int32Array(t),this.c=new Int32Array(16),this.r=new Int32Array(3),this.u=new Int32Array(i),this.x=new Int32Array(16)),this.v.length100?k(new Uint8Array(t.buffer,t.byteOffset+i,r),e,n):function(t,i,e,n,r){for(var s=0;s{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},e.o=(t,i)=>Object.prototype.hasOwnProperty.call(t,i);var n={};(()=>{"use strict";e.d(n,{default:()=>q});function t(t){return r(i(s(t)))}function i(t){return o(h(a(t),8*t.length))}function r(t){for(var i="",e=t.length,n=0;n8*t.length?i+="":i+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(r>>>6*(3-s)&63);return i}function s(t){for(var i,e,n="",r=-1;++r>>6&31,128|63&i):i<=65535?n+=String.fromCharCode(224|i>>>12&15,128|i>>>6&63,128|63&i):i<=2097151&&(n+=String.fromCharCode(240|i>>>18&7,128|i>>>12&63,128|i>>>6&63,128|63&i));return n}function a(t){for(var i=Array(t.length>>2),e=0;e>5]|=(255&t.charCodeAt(e/8))<<24-e%32;return i}function o(t){for(var i="",e=0;e<32*t.length;e+=8)i+=String.fromCharCode(t[e>>5]>>>24-e%32&255);return i}function h(t,i){t[i>>5]|=128<<24-i%32,t[15+(i+64>>9<<4)]=i;for(var e=Array(80),n=1732584193,r=-271733879,s=-1732584194,a=271733878,o=-1009589776,h=0;h>16)+(i>>16)+(e>>16)<<16|65535&e}function d(t,i){return t<>>32-i}function _(t){this.value=t,this.listeners=[]}function c(){this.queue=[]}_.prototype.addListener=function(t){this.listeners.push(t)},_.prototype.addListenerAndFire=function(t){this.listeners.push(t),t(this.value)},_.prototype.removeListener=function(t){var i,e;i=this.listeners,(e=function(t,i){if(!t)return-1;for(var e=0;e=0&&i.splice(e,1)},_.prototype.get=function(){return this.value},_.prototype.set=function(t){this.value=t;for(var i=0;i=0&&navigator.userAgent.indexOf("Chrome")<0;function m(t){if(!t)return null;for(var i=new Uint8Array(t.length),e=0;e1e8)throw"Monster fetch!";r.setRequestHeader("Range","bytes="+e.start+"-"+e.end),e.end-e.start+1}r.onreadystatechange=function(){if(4==r.readyState)return 200==r.status||206==r.status?i(r.responseText):i(null)},e.opts.credentials&&(r.withCredentials=!0),r.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))},b.prototype.salted=function(){var t=function(t){var i={};for(var e in t)i[e]=t[e];return i}(this.opts);return t.salt=!0,new b(this.url,this.start,this.end,t)},b.prototype.getURL=function(){return this.opts.resolver?this.opts.resolver(this.url).then((function(t){return"string"==typeof t?t:t.url})):Promise.resolve(this.url)},b.prototype.fetch=function(i,e){var n=this,r=(e=e||{}).attempt||1,s=e.truncatedLength;if(r>3)return i(null);this.getURL().then((function(a){try{var o;e.timeout&&!n.opts.credentials&&(o=setTimeout((function(){return console.log("timing out "+a),l.abort(),i(null,"Timeout")}),e.timeout));var h,l=new XMLHttpRequest;if((p||n.opts.salt)&&a.indexOf("?")<0&&(a=a+"?salt="+t(Date.now()+","+ ++v)),l.open("GET",a,!0),l.overrideMimeType("text/plain; charset=x-user-defined"),n.end){if(n.end-n.start>1e8)throw"Monster fetch!";l.setRequestHeader("Range","bytes="+n.start+"-"+n.end),h=n.end-n.start+1}l.responseType="arraybuffer",l.onreadystatechange=function(){if(4==l.readyState){if(o&&clearTimeout(o),200==l.status||206==l.status){if(l.response){var t=l.response.byteLength;return!h||h==t||s&&t==s?i(l.response):n.fetch(i,{attempt:r+1,truncatedLength:t})}if(l.mozResponseArrayBuffer)return i(l.mozResponseArrayBuffer);var e=l.responseText;return!h||h==e.length||s&&e.length==s?i(m(l.responseText)):n.fetch(i,{attempt:r+1,truncatedLength:e.length})}return n.fetch(i,{attempt:r+1})}},n.opts.credentials&&(l.withCredentials=!0),l.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))};var w=new ArrayBuffer(8);new Uint8Array(w),new Float32Array(w);function y(t,i){return t[i+3]<<24|t[i+2]<<16|t[i+1]<<8|t[i]}var g=e(398);function k(t,i){this.block=t,this.offset=i}function A(t,i,e){var n=4294967296*(255&t[i+6])+16777216*(255&t[i+5])+65536*(255&t[i+4])+256*(255&t[i+3])+(255&t[i+2]),r=t[i+1]<<8|t[i];return 0!=n||0!=r||e?new k(n,r):null}function C(t,i){i=Math.min(i||1,t.byteLength-50);for(var e=[],n=[0],r=0;n[0]n._max&&(n._max=t._max):(e.push(n),n=t)})),e.push(n),this._ranges=e}function L(t,i){return t._mini._min?1:t._maxt._max?1:0}R.prototype.min=function(){return this._min},R.prototype.max=function(){return this._max},R.prototype.contains=function(t){return t>=this._min&&t<=this._max},R.prototype.isContiguous=function(){return!0},R.prototype.ranges=function(){return[this]},R.prototype._pushRanges=function(t){t.push(this)},R.prototype.toString=function(){return"["+this._min+"-"+this._max+"]"},I.prototype.min=function(){return this._ranges[0].min()},I.prototype.max=function(){return this._ranges[this._ranges.length-1].max()},I.prototype.lower_bound=function(t){var i=this.ranges();if(t>this.max())return i.length;if(ti[r]._max)e=r+1;else{if(!(tt._max&&(t._max=e[n]._max),this._ranges.splice(i,n-i+1,t)}}else this._ranges.push(t)},I.prototype.isContiguous=function(){return this._ranges.length>1},I.prototype.ranges=function(){return this._ranges},I.prototype._pushRanges=function(t){for(var i=0;i0&&(t+=","),t+=this._ranges[i].toString();return t};function T(){}function U(t,i){return new Promise((function(e,n){var r,s,a,o;r=new b(t),s=new b(i),a=function(t,i){i?n(i):e(t)},(o=new T).data=r,o.tbi=s,o.tbi.fetch((function(t){if(!t)return a(null,"Couldn't access Tabix");var i=C(t,t.byteLength),e=new Uint8Array(i);if(21578324!=y(e,0))return a(null,"Not a tabix index");var n=y(e,4);o.format=y(e,8),o.colSeq=y(e,12),o.colStart=y(e,16),o.colEnd=y(e,20),o.meta=y(e,24),o.skip=y(e,28),y(e,32),o.indices=[];var r=36;o.chrToIndex={},o.indexToChr=[];for(var s=0;s0&&(p+=65536),p0&&(o.indices[u]=new Uint8Array(i,d,r-d))}o.headerMax=f,a(o)}),{timeout:5e4})}))}function E(t){const i=t.Adapters.get("BaseLZAdapter");t.Adapters.add("TabixUrlSource",class extends i{constructor(t){if(super(t),!t.parser_func||!t.url_data&&!t.reader)throw new Error("Tabix source is missing required configuration options");if(this.parser=t.parser_func,this.url_data=t.url_data,this.url_tbi=t.url_tbi||`${this.url_data}.tbi`,this._overfetch=t.overfetch||0,this._overfetch<0||this._overfetch>1)throw new Error("Overfetch must be specified as a fraction (0-1) of the requested region size");this.url_data?this._reader_promise=U(this.url_data,this.url_tbi).catch((function(){throw new Error("Failed to create a tabix reader from the provided URL")})):this._reader_promise=Promise.resolve(t.reader)}_performRequest(t){return new Promise(((i,e)=>{const n=t.start,r=t.end,s=this._overfetch*(r-n),a=t.start-s,o=t.end+s;this._reader_promise.then((n=>{n.fetch(t.chr,a,o,(function(t,n){n&&e(new Error("Could not read requested region. This may indicate an error with the .tbi index.")),i(t)}))}))}))}_normalizeResponse(t){return t.map(this.parser)}})}T.prototype.blocksForRange=function(t,i,e){var n=this.indices[t];if(!n)return[];for(var r=function(t,i){var e,n=[];for(--i,n.push(0),e=1+(t>>26);e<=1+(i>>26);++e)n.push(e);for(e=9+(t>>23);e<=9+(i>>23);++e)n.push(e);for(e=73+(t>>20);e<=73+(i>>20);++e)n.push(e);for(e=585+(t>>17);e<=585+(i>>17);++e)n.push(e);for(e=4681+(t>>14);e<=4681+(i>>14);++e)n.push(e);return n}(i,e),s=[],a=0;a>14,v-1),w=Math.min(e>>14,v-1);for(a=m;a<=w;++a){var g=A(n,f+4+8*a);g&&((!p||g.block=p.block&&C.maxv.offset>=p.offset&&k.push(C)}h=k;var R=[];for(a=0;a0){var L=R[0];for(a=1;a=a.length)return n(l);if(h){var s=new Uint8Array(h);return r.readRecords(s,a[f].minv.offset,l,i,e,o),h=null,++f,t()}var u=a[f],d=u.minv.block,_=u.maxv.block+65536;r.data.slice(d,_-d).fetch((function(i){try{return h=C(i,u.maxv.block-u.minv.block+1),t()}catch(t){return n(null,t)}}))}()}catch(t){n(null,t)}},T.prototype.readRecords=function(t,i,e,n,r,s){t:for(;;){for(var a="";i0&&(f=parseInt(h[this.colEnd-1])),65536&this.format&&++l,l<=r&&f>=n&&e.push(a)}continue t}a+=String.fromCharCode(o)}return}},T.prototype.fetchHeader=function(t,i){var e={metaOnly:!0,skipped:!1,nLines:0};i=i||e,Object.keys(e).forEach((function(t){i.hasOwnProperty(t)||(i[t]=e[t])}));var n=this;n.data.slice(0,n.headerMax).fetch((function(e){if(!e)return t(null,"Fetch failed");for(var r=new Uint8Array(C(e,e.byteLength)),s=0,a="",o=[];s{"use strict";var t={d:(e,o)=>{for(var a in o)t.o(o,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:o[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>i});d3;Math.sqrt(3);function o(t){return JSON.parse(JSON.stringify(t))}const a=["highlight","select","fade","hide"],l=["highlighted","selected","faded","hidden"],s=["unhighlight","deselect","unfade","show"];function n(t){const e=t.Widgets.get("_Button"),n=t.Widgets.get("BaseWidget");const i=function(){const e=t.Layouts.get("tooltip","standard_association");return e.html+='
    Condition on Variant
    ',e}(),r=function(){const e=t.Layouts.get("toolbar","standard_association");return e.widgets.push({type:"covariates_model",button_html:"Model",button_title:"Show and edit covariates currently in model",position:"left"}),e}();t.Widgets.add("covariates_model",class extends n{initialize(){this.parent_plot.state.model=this.parent_plot.state.model||{},this.parent_plot.state.model.covariates=this.parent_plot.state.model.covariates||[],this.parent_plot.CovariatesModel={button:this,add:t=>{const e=this.parent_plot,a=o(t);"object"==typeof t&&"string"!=typeof a.html&&(a.html="function"==typeof t.toHTML?t.toHTML():t.toString());for(let t=0;t{const e=this.parent_plot;if(void 0===e.state.model.covariates[t])throw new Error(`Unable to remove model covariate, invalid index: ${t.toString()}`);return e.state.model.covariates.splice(t,1),e.applyState(),e.CovariatesModel.updateWidget(),e},removeAll:()=>{const t=this.parent_plot;return t.state.model.covariates=[],t.applyState(),t.CovariatesModel.updateWidget(),t},updateWidget:()=>{this.button.update(),this.button.menu.update()}}}update(){return this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=this.button.menu.inner_selector;if(t.html(""),void 0!==this.parent_plot.state.model.html&&t.append("div").html(this.parent_plot.state.model.html),this.parent_plot.state.model.covariates.length){t.append("h5").html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);const e=t.append("table");this.parent_plot.state.model.covariates.forEach(((t,o)=>{const a="object"==typeof t&&"string"==typeof t.html?t.html:t.toString(),l=e.append("tr");l.append("td").append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","0em").on("click",(()=>this.parent_plot.CovariatesModel.removeByIdx(o))).html("×"),l.append("td").html(a)})),t.append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","4px").html("× Remove All Covariates").on("click",(()=>this.parent_plot.CovariatesModel.removeAll()))}else t.append("i").html("no covariates in model")})),this.button.preUpdate=()=>{let t="Model";const e=this.parent_plot.state.model.covariates.length;if(e){t+=` (${e} ${e>1?"covariates":"covariate"})`}this.button.setHtml(t).disable(!1)},this.button.show()),this}}),t.Widgets.add("data_layers",class extends n{update(){return"string"!=typeof this.layout.button_html&&(this.layout.button_html="Data Layers"),"string"!=typeof this.layout.button_title&&(this.layout.button_title="Manipulate Data Layers (sort, dim, show/hide, etc.)"),this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html("");const t=this.button.menu.inner_selector.append("table");return this.parent_panel._data_layer_ids_by_z_index.slice().reverse().forEach(((e,o)=>{const n=this.parent_panel.data_layers[e],i="string"!=typeof n.layout.name?n.id:n.layout.name,r=t.append("tr");r.append("td").html(i),this.layout.statuses.forEach((t=>{const e=l.indexOf(t),o=a[e];let i,d,u;n._global_statuses[t]?(i=s[e],d=`un${o}AllElements`,u="-highlighted"):(i=a[e],d=`${o}AllElements`,u=""),r.append("td").append("a").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}${u}`).style("margin-left","0em").on("click",(()=>{n[d](),this.button.menu.populate()})).html(i)}));const d=0===o,u=o===this.parent_panel._data_layer_ids_by_z_index.length-1,p=r.append("td");p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${u?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveBack(),this.button.menu.populate()})).html("▾").attr("title","Move layer down (further back)"),p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${d?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveForward(),this.button.menu.populate()})).html("▴").attr("title","Move layer up (further front)"),p.append("a").attr("class","lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red").style("margin-left","0em").on("click",(()=>(confirm(`Are you sure you want to remove the ${i} layer? This cannot be undone.`)&&n.parent.removeDataLayer(e),this.button.menu.populate()))).html("×").attr("title","Remove layer")})),this})),this.button.show()),this}}),t.Layouts.add("tooltip","covariates_model_association",i),t.Layouts.add("toolbar","covariates_model_plot",r)}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const i=n;LzWidgetAddons=e.default})(); //# sourceMappingURL=lz-widget-addons.min.js.map \ No newline at end of file diff --git a/dist/locuszoom.app.min.js b/dist/locuszoom.app.min.js index a11e27da..9477f929 100644 --- a/dist/locuszoom.app.min.js +++ b/dist/locuszoom.app.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.1 */ -var LocusZoom;(()=>{var t={483:(t,e,s)=>{"use strict";const i=s(919);t.exports=function(t,...e){if(!t){if(1===e.length&&e[0]instanceof Error)throw e[0];throw new i(e)}}},919:(t,e,s)=>{"use strict";const i=s(820);t.exports=class extends Error{constructor(t){super(t.filter((t=>""!==t)).map((t=>"string"==typeof t?t:t instanceof Error?t.message:i(t))).join(" ")||"Unknown error"),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e.assert)}}},820:t=>{"use strict";t.exports=function(...t){try{return JSON.stringify.apply(null,t)}catch(t){return"[Cannot display object: "+t.message+"]"}}},681:(t,e,s)=>{"use strict";const i=s(483),a={};e.B=class{constructor(){this._items=[],this.nodes=[]}add(t,e){const s=[].concat((e=e||{}).before||[]),a=[].concat(e.after||[]),o=e.group||"?",n=e.sort||0;i(!s.includes(o),`Item cannot come before itself: ${o}`),i(!s.includes("?"),"Item cannot come before unassociated items"),i(!a.includes(o),`Item cannot come after itself: ${o}`),i(!a.includes("?"),"Item cannot come after unassociated items"),Array.isArray(t)||(t=[t]);for(const e of t){const t={seq:this._items.length,sort:n,before:s,after:a,group:o,node:e};this._items.push(t)}if(!e.manual){const t=this._sort();i(t,"item","?"!==o?`added into group ${o}`:"","created a dependencies error")}return this.nodes}merge(t){Array.isArray(t)||(t=[t]);for(const e of t)if(e)for(const t of e._items)this._items.push(Object.assign({},t));this._items.sort(a.mergeSort);for(let t=0;tt.sort===e.sort?0:t.sort{function e(t){if("string"==typeof t.source.flags)return t.source.flags;var e=[];return t.global&&e.push("g"),t.ignoreCase&&e.push("i"),t.multiline&&e.push("m"),t.sticky&&e.push("y"),t.unicode&&e.push("u"),e.join("")}t.exports=function t(s){if("function"==typeof s)return s;var i=Array.isArray(s)?[]:{};for(var a in s){var o=s[a],n={}.toString.call(o).slice(8,-1);i[a]="Array"==n||"Object"==n?t(o):"Date"==n?new Date(o.getTime()):"RegExp"==n?RegExp(o.source,e(o)):o}return i}}},e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={exports:{}};return t[i](a,a.exports,s),a.exports}s.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return s.d(e,{a:e}),e},s.d=(t,e)=>{for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),s.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";s.d(i,{default:()=>ds});var t={};s.r(t),s.d(t,{AssociationLZ:()=>M,BaseAdapter:()=>$,BaseApiAdapter:()=>z,BaseLZAdapter:()=>k,BaseUMAdapter:()=>E,GeneConstraintLZ:()=>A,GeneLZ:()=>N,GwasCatalogLZ:()=>S,LDServer:()=>O,PheWASLZ:()=>j,RecombLZ:()=>T,StaticSource:()=>L});var e={};s.r(e),s.d(e,{htmlescape:()=>F,is_numeric:()=>H,log10:()=>C,logtoscinotation:()=>U,neglog10:()=>B,scinotation:()=>q,urlencode:()=>G});var a={};s.r(a),s.d(a,{BaseWidget:()=>yt,_Button:()=>ft,display_options:()=>Ot,download:()=>vt,download_png:()=>wt,filter_field:()=>xt,menu:()=>St,move_panel_down:()=>kt,move_panel_up:()=>zt,region_scale:()=>bt,remove_panel:()=>$t,resize_to_data:()=>Nt,set_state:()=>Tt,shift_region:()=>Et,title:()=>mt,toggle_legend:()=>At,zoom_region:()=>Mt});var o={};s.r(o),s.d(o,{categorical_bin:()=>Vt,effect_direction:()=>Qt,if_value:()=>Zt,interpolate:()=>Xt,numerical_bin:()=>Kt,ordinal_cycle:()=>Wt,stable_choice:()=>Yt});var n={};s.r(n),s.d(n,{BaseDataLayer:()=>ie,annotation_track:()=>oe,arcs:()=>he,category_scatter:()=>me,genes:()=>de,highlight_regions:()=>re,line:()=>_e,orthogonal_line:()=>ge,scatter:()=>fe});var r={};s.r(r),s.d(r,{data_layer:()=>es,panel:()=>ss,plot:()=>is,toolbar:()=>ts,toolbar_widgets:()=>Qe,tooltip:()=>Xe});const l="0.14.0-beta.1";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}class d{constructor(t,e,s={},i=null,a=null){this.key=t,this.value=e,this.metadata=s,this.prev=i,this.next=a}}class u{constructor(t=3){if(this._max_size=t,this._cur_size=0,this._store=new Map,this._head=null,this._tail=null,null===t||t<0)throw new Error('Cache "max_size" must be >= 0')}has(t){return this._store.has(t)}get(t){const e=this._store.get(t);return e?(this._head!==e&&this.add(t,e.value),e.value):null}add(t,e,s={}){if(0===this._max_size)return;const i=this._store.get(t);i&&this._remove(i);const a=new d(t,e,s,null,this._head);if(this._head?this._head.prev=a:this._tail=a,this._head=a,this._store.set(t,a),this._max_size>=0&&this._cur_size>=this._max_size){const t=this._tail;this._tail=this._tail.prev,this._remove(t)}this._cur_size+=1}clear(){this._head=null,this._tail=null,this._cur_size=0,this._store=new Map}remove(t){const e=this._store.get(t);return!!e&&(this._remove(e),!0)}_remove(t){null!==t.prev?t.prev.next=t.next:this._head=t.next,null!==t.next?t.next.prev=t.prev:this._tail=t.prev,this._store.delete(t.key),this._cur_size-=1}find(t){let e=this._head;for(;e;){const s=e.next;if(t(e))return e;e=s}}}var _=s(957),p=s.n(_);function g(t){return"object"!=typeof t?t:p()(t)}var y=s(681);function f(t,e,s,i=!0){if(!s.length)return[];const a=s.map((t=>function(t){const e=/^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(t);if(!e)throw new Error(`Unable to parse dependency specification: ${t}`);let{name_alone:s,name_deps:i,deps:a}=e.groups;return s?[s,[]]:(a=a.split(/\s*,\s*/),[i,a])}(t))),o=new Map(a),n=new y.B;for(let[t,e]of o.entries())try{n.add(t,{after:e,group:t})}catch(e){throw new Error(`Invalid or possible circular dependency specification for: ${t}`)}const r=n.nodes,l=new Map;for(let s of r){const i=e.get(s);if(!i)throw new Error(`Data has been requested from source '${s}', but no matching source was provided`);const a=o.get(s)||[],n=Promise.all(a.map((t=>l.get(t)))).then((e=>{const a=Object.assign({_provider_name:s},t);return i.getData(a,...e)}));l.set(s,n)}return Promise.all([...l.values()]).then((t=>i?t[t.length-1]:t))}function m(t,e){const s=new Map;for(let i of t){const t=i[e];if(void 0===t)throw new Error(`All records must specify a value for the field "${e}"`);if("object"==typeof t)throw new Error("Attempted to group on a field with non-primitive values");let a=s.get(t);a||(a=[],s.set(t,a)),a.push(i)}return s}function b(t,e,s,i,a){const o=m(s,a),n=[];for(let s of e){const e=s[i],a=o.get(e)||[];a.length?n.push(...a.map((t=>Object.assign({},g(t),g(s))))):"inner"!==t&&n.push(g(s))}if("outer"===t){const t=m(e,i);for(let e of s){const s=e[a];(t.get(s)||[]).length||n.push(g(e))}}return n}function x(t,e,s,i){return b("left",...arguments)}const v=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function w(t,e=!1){const s=t&&t.match(v);if(s)return s.slice(1);if(e)return null;throw new Error(`Could not understand marker format for ${t}. Should be of format chr:pos or chr:pos_ref/alt`)}class ${constructor(){throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.')}}class z extends ${}class k extends class extends class{constructor(t={}){this._config=t;const{cache_enabled:e=!0,cache_size:s=3}=t;this._enable_cache=e,this._cache=new u(s)}_buildRequestOptions(t,e){return Object.assign({},t)}_getCacheKey(t){if(this._enable_cache)throw new Error("Method not implemented");return null}_performRequest(t){throw new Error("Not implemented")}_normalizeResponse(t,e){return t}_annotateRecords(t,e){return t}_postProcessResponse(t,e){return t}getData(t={},...e){t=this._buildRequestOptions(t,...e);const s=this._getCacheKey(t);let i;return this._enable_cache&&this._cache.has(s)?i=this._cache.get(s):(i=Promise.resolve(this._performRequest(t)).then((e=>this._normalizeResponse(e,t))),this._cache.add(s,i,t._cache_meta),i.catch((t=>this._cache.remove(s)))),i.then((t=>g(t))).then((e=>this._annotateRecords(e,t))).then((e=>this._postProcessResponse(e,t)))}}{constructor(t={}){super(t),this._url=t.url}_getCacheKey(t){return this._getURL(t)}_getURL(t){return this._url}_performRequest(t){const e=this._getURL(t);if(!this._url)throw new Error('Web based resources must specify a resource URL as option "url"');return fetch(e).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}_normalizeResponse(t,e){return"string"==typeof t?JSON.parse(t):t}}{constructor(t={}){t.params&&(console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'),Object.assign(t,t.params||{}),delete t.params),super(t);const{prefix_namespace:e=!0,limit_fields:s}=t;this._prefix_namespace=e,this._limit_fields=!!s&&new Set(s)}_getCacheKey(t){let{chr:e,start:s,end:i}=t;const a=this._cache.find((({metadata:t})=>e===t.chr&&s>=t.start&&i<=t.end));return a&&({chr:e,start:s,end:i}=a.metadata),t._cache_meta={chr:e,start:s,end:i},`${e}_${s}_${i}`}_postProcessResponse(t,e){if(!this._prefix_namespace||!Array.isArray(t))return t;const{_limit_fields:s}=this,{_provider_name:i}=e;return t.map((t=>Object.entries(t).reduce(((t,[e,a])=>(s&&!s.has(e)||(t[`${i}:${e}`]=a),t)),{})))}_findPrefixedKey(t,e){const s=new RegExp(`:${e}$`),i=Object.keys(t).find((t=>s.test(t)));if(!i)throw new Error(`Could not locate the required key name: ${e} in dependent data`);return i}}class E extends k{constructor(t={}){super(t),this._genome_build=t.genome_build||t.build}_validateBuildSource(t,e){if(t&&e||!t&&!e)throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`)}_normalizeResponse(t,e){let s=super._normalizeResponse(...arguments);if(s=s.data||s,Array.isArray(s))return s;const i=Object.keys(s),a=s[i[0]].length;if(!i.every((function(t){return s[t].length===a})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const o=[],n=Object.keys(s);for(let t=0;t25||"GRCh38"===s)return Promise.resolve([]);e=`{${e.join(" ")} }`;const i=this._getURL(t),a=JSON.stringify({query:e});return fetch(i,{method:"POST",body:a,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}_normalizeResponse(t){if("string"!=typeof t)return t;return JSON.parse(t).data}}class O extends E{constructor(t){t.limit_fields||(t.limit_fields=["variant2","position2","correlation"]),super(t)}__find_ld_refvar(t,e){const s=this._findPrefixedKey(e[0],"variant"),i=this._findPrefixedKey(e[0],"log_pvalue");let a,o={};if(t.ldrefvar)a=t.ldrefvar,o=e.find((t=>t[s]===a))||{};else{let t=0;for(let n of e){const{[s]:e,[i]:r}=n;r>t&&(t=r,a=e,o=n)}}o.lz_is_ld_refvar=!0;const n=w(a,!0);if(!n)throw new Error("Could not request LD for a missing or incomplete marker format");const[r,l,h,c]=n;a=`${r}:${l}`,h&&c&&(a+=`_${h}/${c}`);const d=+l;return d&&t.ldrefvar&&t.chr&&(r!==t.chr||dt.end)?(t.ldrefvar=null,this.__find_ld_refvar(t,e)):a}_buildRequestOptions(t,e){if(!e)throw new Error("LD request must depend on association data");const s=super._buildRequestOptions(...arguments);if(!e.length)return s._skip_request=!0,s;s.ld_refvar=this.__find_ld_refvar(t,e);const i=t.genome_build||this._config.build||"GRCh37";let a=t.ld_source||this._config.source||"1000G";const o=t.ld_pop||this._config.population||"ALL";return"1000G"===a&&"GRCh38"===i&&(a="1000G-FRZ09"),this._validateBuildSource(i,null),Object.assign({},s,{genome_build:i,ld_source:a,ld_population:o})}_getURL(t){const e=this._config.method||"rsquare",{chr:s,start:i,end:a,ld_refvar:o,genome_build:n,ld_source:r,ld_population:l}=t;return[super._getURL(t),"genome_builds/",n,"/references/",r,"/populations/",l,"/variants","?correlation=",e,"&variant=",encodeURIComponent(o),"&chrom=",encodeURIComponent(s),"&start=",encodeURIComponent(i),"&stop=",encodeURIComponent(a)].join("")}_getCacheKey(t){const e=super._getCacheKey(t),{ld_refvar:s,ld_source:i,ld_population:a}=t;return`${e}_${s}_${i}_${a}`}_performRequest(t){if(t._skip_request)return Promise.resolve([]);const e=this._getURL(t);let s={data:{}},i=function(t){return fetch(t).then().then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){s.data[e]=(s.data[e]||[]).concat(t.data[e])})),t.next?i(t.next):s}))};return i(e)}}class T extends E{constructor(t){t.limit_fields||(t.limit_fields=["position","recomb_rate"]),super(t)}_getURL(t){const e=t.genome_build||this._config.build;let s=this._config.source;this._validateBuildSource(e,s);const i=e?`&build=${e}`:` and id in ${s}`;return`${super._getURL(t)}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${i}`}}class L extends k{constructor(t={}){super(...arguments);const{data:e}=t;if(!e||Array.isArray(t))throw new Error("'StaticSource' must provide data as required option 'config.data'");this._data=e}_performRequest(t){return Promise.resolve(this._data)}}class j extends E{_getURL(t){const e=(t.genome_build?[t.genome_build]:null)||this._config.build;if(!e||!Array.isArray(e)||!e.length)throw new Error(["Adapter",this.constructor.name,"requires that you specify array of one or more desired genome build names"].join(" "));return[super._getURL(t),"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",e.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}_getCacheKey(t){return this._getURL(t)}}const P=new c;for(let[e,s]of Object.entries(t))P.add(e,s);P.add("StaticJSON",L),P.add("LDLZ2",O);const R=P,I=d3,D={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function C(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function B(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function U(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function q(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function F(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function H(t){return"number"==typeof t}function G(t){return encodeURIComponent(t)}const J=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,s]of Object.entries(e))J.add(t,s);const Z=J;class K{constructor(t){if(!/^(?:\w+:\w+|^\w+)(?:\|\w+)*$/.test(t))throw new Error(`Invalid field specifier: '${t}'`);const[e,...s]=t.split("|");this.full_name=t,this.field_name=e,this.transformations=s.map((t=>Z.get(t)))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[this.field_name]?s=t[this.field_name]:e&&void 0!==e[this.field_name]&&(s=e[this.field_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const V=/^(\*|[\w]+)/,W=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function Y(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=V.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=V.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=W.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function X(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function Q(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const o=t[s.attr];1===e.length?void 0!==o&&a.push([s.attr]):a.push(...Q(o,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...Q(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...Q(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[o,n]of Object.entries(t))a.push(...Q(n,e).map((t=>[o].concat(t)))),"*"===s.attr&&a.push(...Q(n,i).map((t=>[o].concat(t))))}}else if(s.attrs)for(let[e,o]of Object.entries(t)){const[t,n,r]=X(o,s.attrs);r===s.value&&a.push(...Q(o,i).map((t=>[e].concat(t))))}const o=(n=a,r=JSON.stringify,[...new Map(n.map((t=>[r(t),t]))).values()]);var n,r;return o.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),o}function tt(t,e){const s=function(t,e){let s=[];for(let i of Q(t,e))s.push(X(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=Y(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const et=Math.sqrt(3),st={draw(t,e){const s=-Math.sqrt(e/(3*et));t.moveTo(0,2*-s),t.lineTo(-et*s,s),t.lineTo(et*s,s),t.closePath()}};function it(t,e){if(e=e||{},!t||"object"!=typeof t||"object"!=typeof e)throw new Error("Layout and shared namespaces must be provided as objects");for(let[s,i]of Object.entries(t))"namespace"===s?Object.keys(i).forEach((t=>{const s=e[t];s&&(i[t]=s)})):null!==i&&"object"==typeof i&&(t[s]=it(i,e));return t}function at(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=at(t[s],e[s])):t[s]=ot(e[s])}return t}function ot(t){return JSON.parse(JSON.stringify(t))}function nt(t){if(!t)return null;if("triangledown"===t)return st;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return I[e]||null}function rt(t,e,s=null){const i=new Set;if(!s){if(!e.length)return i;const t=e.join("|");s=new RegExp(`(?:{{)?(?:#if *)?((?:${t}):\\w+)`,"g")}for(const a of Object.values(t)){const t=typeof a;let o=[];if("string"===t){let t;for(;null!==(t=s.exec(a));)o.push(t[1])}else{if(null===a||"object"!==t)continue;o=rt(a,e,s)}for(let t of o)i.add(t)}return i}function lt(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>lt(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,o)=>(a[o]=lt(t[o],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const o=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(o,s)}}function ht(t,e,s){return function(t,e,s){return tt(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function ct(t,e){return function(t,e){return tt(t,e).map((t=>t[2]))}(t,e)}class dt{constructor(t,e,s){this._callable=ls.get(t),this._initiator=e,this._params=s||[]}getData(t,...e){const s={plot_state:t,data_layer:this._initiator};return Promise.resolve(this._callable(s,e,...this._params))}}const ut=class{constructor(t){this._sources=t}config_to_sources(t={},e=[],s){const i=new Map,a=Object.keys(t);let o=e.find((t=>"fetch"===t.type));o||(o={type:"fetch",from:a},e.unshift(o));const n=/^\w+$/;for(let[e,s]of Object.entries(t)){if(!n.test(e))throw new Error(`Invalid namespace name: '${e}'. Must contain only alphanumeric characters`);const t=this._sources.get(s);if(!t)throw new Error(`A data layer has requested an item not found in DataSources: data type '${e}' from ${s}`);i.set(e,t),o.from.find((t=>t.split("(")[0]===e))||o.from.push(e)}let r=Array.from(o.from);for(let t of e){let{type:e,name:a,requires:o,params:n}=t;if("fetch"!==e){let l=0;if(a||(a=t.name=`join${l}`,l+=1),i.has(a))throw new Error(`Configuration error: within a layer, join name '${a}' must be unique`);o.forEach((t=>{if(!i.has(t))throw new Error(`Data operation cannot operate on unknown provider '${t}'`)}));const h=new dt(e,s,n);i.set(a,h),r.push(`${a}(${o.join(", ")})`)}}return[i,r]}getData(t,e,s){return s.length?f(t,e,s,!0):Promise.resolve([])}};function _t(){return{showing:!1,selector:null,content_selector:null,hide_delay:null,show:(t,e)=>(this.curtain.showing||(this.curtain.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&>(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function pt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function gt(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class yt{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&>(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class ft{constructor(t){if(!(t instanceof yt))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),o=this.menu.outer_selector.node().getBoundingClientRect(),n=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-o.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-o.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),_=Math.min(n+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${_}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(gt,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class mt extends yt{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class bt extends yt{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(qt(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&>(this.selector,this.layout.style),this}}class xt extends yt{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class vt extends yt{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=I.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+I.select(this).attr("dy").substring(-2).slice(0,-2);I.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class wt extends vt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((o,n)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{o(URL.createObjectURL(t))}))},r.src=t}))}))}}class $t extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),I.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),I.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class zt extends yt{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class kt extends yt{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot._panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class Et extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${qt(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class Mt extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class St extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class Nt extends yt{constructor(t){super(...arguments)}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class At extends yt{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new ft(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class Ot extends yt{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,o={};s.forEach((t=>{const e=a[t];void 0!==e&&(o[t]=ot(e))})),this._selected_item="default",this.button=new ft(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,n=(a,n,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==n[t];i.layout[t]=e?n[t]:o[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return n(r,o,"default"),a.options.forEach(((t,e)=>n(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class Tt extends yt{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new ft(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,o)=>{const n=s.append("tr"),r=`${e}${o}`;n.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",o).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),n.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const Lt=new c;for(let[t,e]of Object.entries(a))Lt.add(t,e);const jt=Lt;class Pt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{this.addWidget(t)})),"panel"===this.type&&I.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}addWidget(t){try{const e=jt.create(t.type,t,this);return this.widgets.push(e),e}catch(t){console.warn("Failed to create widget"),console.error(t)}}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot._panel_boundaries.dragging||this.parent_plot._interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=I.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=I.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const Rt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:12,hidden:!1};class It{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=at(this.parent.layout.legend||{},Rt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((a=>{Array.isArray(this.parent.data_layers[a].layout.legend)&&this.parent.data_layers[a].layout.legend.forEach((a=>{const o=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),n=+a.label_size||+this.layout.label_size||12;let r=0,l=n/2+t/2;i=Math.max(i,n+t);const h=a.shape||"",c=nt(h);if("line"===h){const e=+a.length||16,s=n/4+t/2;o.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(gt,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;o.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(gt,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if(c){const e=+a.size||40,s=Math.ceil(Math.sqrt(e/Math.PI));o.append("path").attr("class",a.class||"").attr("d",I.symbol().size(e).type(c)).attr("transform",`translate(${s}, ${s+t/2})`).attr("fill",a.color||{}).call(gt,a.style||{}),r=2*s+t,l=Math.max(2*s+t/2,l),i=Math.max(i,2*s+t)}o.append("text").attr("text-anchor","left").attr("class","lz-label").attr("x",r).attr("y",l).style("font-size",n).text(a.label);const d=o.node().getBoundingClientRect();if("vertical"===this.layout.orientation)s+=d.height+t,i=0;else{const a=this.layout.origin.x+e+d.width;e>t&&a>this.parent.parent.layout.width&&(s+=i,e=t,o.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(o)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const Dt={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class Ct{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"!=typeof t.id||!t.id)throw new Error('Panel layouts must specify "id"');if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`);this.id=t.id,this._initialized=!1,this._layout_idx=null,this.svg={},this.layout=at(t||{},Dt),this.parent?(this.state=this.parent.state,this._state_id=this.id,this.state[this._state_id]=this.state[this._state_id]||{}):(this.state=null,this._state_id=null),this.data_layers={},this._data_layer_ids_by_z_index=[],this._data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this._zoom_timeout=null,this._event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this._event_hooks[t]&&this._event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=at(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(gt,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id '${t.id}'; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=xe.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this._data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this._data_layer_ids_by_z_index.length+e.layout.z_index,0)),this._data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this._data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id]._layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){const e=this.data_layers[t];if(!e)throw new Error(`Unable to remove data layer, ID not found: ${t}`);return e.destroyAllTooltips(),e.svg.container&&e.svg.container.remove(),this.layout.data_layers.splice(e._layout_idx,1),delete this.state[e._state_id],delete this.data_layers[t],this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id]._layout_idx=e})),this}clearSelections(){return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height);const{cliparea:t}=this.layout,{margin:e}=this.layout;this.inner_border.attr("x",e.left).attr("y",e.top).attr("width",this.parent_plot.layout.width-(e.left+e.right)).attr("height",this.layout.height-(e.top+e.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const s=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),o=Math.pow(10,e);return t===1/0&&(t=o),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,o),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},i={},a=this.layout.axes;if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};a.x.range&&(t.start=a.x.range.start||t.start,t.end=a.x.range.end||t.end),i.x=[t.start,t.end],i.x_shifted=[t.start,t.end]}if(this.y1_extent){const e={start:t.height,end:0};a.y1.range&&(e.start=a.y1.range.start||e.start,e.end=a.y1.range.end||e.end),i.y1=[e.start,e.end],i.y1_shifted=[e.start,e.end]}if(this.y2_extent){const e={start:t.height,end:0};a.y2.range&&(e.start=a.y2.range.start||e.start,e.end=a.y2.range.end||e.end),i.y2=[e.start,e.end],i.y2_shifted=[e.start,e.end]}let{_interaction:o}=this.parent;const n=o.dragging;if(o.panel_id&&(o.panel_id===this.id||o.linked_panel_ids.includes(this.id))){let a,r=null;if(o.zooming&&"function"==typeof this.x_scale){const s=Math.abs(this.x_extent[1]-this.x_extent[0]),n=Math.round(this.x_scale.invert(i.x_shifted[1]))-Math.round(this.x_scale.invert(i.x_shifted[0]));let r=o.zooming.scale;const l=Math.floor(n*(1/r));r<1&&!isNaN(this.parent.layout.max_region_scale)?r=1/(Math.min(l,this.parent.layout.max_region_scale)/n):r>1&&!isNaN(this.parent.layout.min_region_scale)&&(r=1/(Math.max(l,this.parent.layout.min_region_scale)/n));const h=Math.floor(s*r);a=o.zooming.center-e.left-this.layout.origin.x;const c=a/t.width,d=Math.max(Math.floor(this.x_scale.invert(i.x_shifted[0])-(h-n)*c),1);i.x_shifted=[this.x_scale(d),this.x_scale(d+h)]}else if(n)switch(n.method){case"background":i.x_shifted[0]=+n.dragged_x,i.x_shifted[1]=t.width+n.dragged_x;break;case"x_tick":I.event&&I.event.shiftKey?(i.x_shifted[0]=+n.dragged_x,i.x_shifted[1]=t.width+n.dragged_x):(a=n.start_x-e.left-this.layout.origin.x,r=s(a/(a+n.dragged_x),3),i.x_shifted[0]=0,i.x_shifted[1]=Math.max(t.width*(1/r),1));break;case"y1_tick":case"y2_tick":{const o=`y${n.method[1]}_shifted`;I.event&&I.event.shiftKey?(i[o][0]=t.height+n.dragged_y,i[o][1]=+n.dragged_y):(a=t.height-(n.start_y-e.top-this.layout.origin.y),r=s(a/(a-n.dragged_y),3),i[o][0]=t.height,i[o][1]=t.height-t.height*(1/r))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(i[t][0]),this[`${t}_scale`].invert(i[t][1])],this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!I.event.shiftKey&&!I.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(I.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=I.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,I.event.wheelDelta||-I.event.detail||-I.event.deltaY));0!==e&&(this.parent._interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),o=this.parent._interaction,o.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this._zoom_timeout&&clearTimeout(this._zoom_timeout),this._zoom_timeout=setTimeout((()=>{this.parent._interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this._initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{const e=this.layout.axes[t];Object.keys(e).length&&!1!==e.render?(e.render=!0,e.label=e.label||null):e.render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){const s=this.layout;return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),s.height=Math.max(Math.round(+e),s.min_height)),s.cliparea.width=Math.max(this.parent_plot.layout.width-(s.margin.left+s.margin.right),0),s.cliparea.height=Math.max(s.height-(s.margin.top+s.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",s.height),this._initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this._initialized&&this.render(),this}setMargin(t,e,s,i){let a;const{cliparea:o,margin:n}=this.layout;return!isNaN(t)&&t>=0&&(n.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(n.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(n.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(n.left=Math.max(Math.round(+i),0)),n.top+n.bottom>this.layout.height&&(a=Math.floor((n.top+n.bottom-this.layout.height)/2),n.top-=a,n.bottom-=a),n.left+n.right>this.parent_plot.layout.width&&(a=Math.floor((n.left+n.right-this.parent_plot.layout.width)/2),n.left-=a,n.right-=a),["top","right","bottom","left"].forEach((t=>{n[t]=Math.max(n[t],0)})),o.width=Math.max(this.parent_plot.layout.width-(n.left+n.right),0),o.height=Math.max(this.layout.height-(n.top+n.bottom),0),o.origin.x=n.left,o.origin.y=n.top,this._initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=_t.call(this),this.loader=pt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new Pt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new It(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this._data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(I.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent._panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){const{parent:t}=this,e=this.layout.y_index;return t._panel_ids_by_y_index[e-1]&&(t._panel_ids_by_y_index[e]=t._panel_ids_by_y_index[e-1],t._panel_ids_by_y_index[e-1]=this.id,t.applyPanelYIndexesToPanelLayouts(),t.positionPanels()),this}moveDown(){const{_panel_ids_by_y_index:t}=this.parent;return t[this.layout.y_index+1]&&(t[this.layout.y_index]=t[this.layout.y_index+1],t[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this._data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this._data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this._data_promises).then((()=>{this._initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=I.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=I.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this._data_layer_ids_by_z_index.reduce(((e,a)=>{const o=s.data_layers[a];return e.concat(o.getTicks(t,i))}),[]).map((t=>{let s={};return s=at(s,e),at(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,o=1.5,n=.5+1.5*o,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(_=parseFloat(_.toFixed(c)));u.push(_),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eqt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=I.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=I.select(this).select("text");s[`${t}_ticks`][i].style&>(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const o=this.layout.axes[t].label||null;return null!==o&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(Ht(o,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof I.select(this).node().focus&&I.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";I.event&&I.event.shiftKey&&(i="move"),I.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){I.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this._data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this._data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}D.verbs.forEach(((t,e)=>{const s=D.adjectives[e],i=`un${t}`;Ct.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Ct.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Bt={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Ut{constructor(t,e,s){this._initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this._panel_ids_by_y_index=[],this._remap_promises=[],this.layout=s,at(this.layout,Bt),this._base_layout=ot(this.layout),this.state=this.layout.state,this.lzd=new ut(e),this._external_listeners=new Map,this._event_hooks={},this._interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this._event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this._event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Ct(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this._panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this._panel_ids_by_y_index.length+e.layout.y_index,0)),this._panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this._panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id]._layout_idx=s,this._initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t]._data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i._layer_state,delete this.layout.state[i._state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){const e=this.panels[t];if(!e)throw new Error(`Unable to remove panel, ID not found: ${t}`);return this._panel_boundaries.hide(),this.clearPanelData(t),e.loader.hide(),e.toolbar.destroy(!0),e.curtain.hide(),e.svg.container&&e.svg.container.remove(),this.layout.panels.splice(e._layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id]._layout_idx=e})),this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this._initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e){const{from_layer:s,namespace:i,data_operations:a,onerror:o}=t,n=o||function(t){console.error("An error occurred while acting on an external callback",t)};if(s){const t=`${this.getBaseId()}.`,i=s.startsWith(t)?s:`${t}${s}`;let a=!1;for(let t of Object.values(this.panels))if(a=Object.values(t.data_layers).some((t=>t.getBaseId()===i)),a)break;if(!a)throw new Error(`Could not subscribe to unknown data layer ${i}`);const o=t=>{if(t.data.layer===i)try{e(t.data.content,this)}catch(t){n(t)}};return this.on("data_from_layer",o),o}if(!i)throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option");const[r,l]=this.lzd.config_to_sources(i,a),h=()=>{try{this.lzd.getData(this.state,r,l).then((t=>e(t,this))).catch(n)}catch(t){n(t)}};return this.on("data_rendered",h),h}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this._remap_promises=[],this.loading_data=!0;for(let t in this.panels)this._remap_promises.push(this.panels[t].reMap());return Promise.all(this._remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e._data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this._initialized=!1,this.svg=null,this.panels=null}mutateLayout(){Object.values(this.panels).forEach((t=>{Object.values(t.data_layers).forEach((t=>t.mutateLayout()))}))}_canInteract(t){t=t||null;const{_interaction:e}=this;return t?(void 0===e.panel_id||e.panel_id===t)&&!this.loading_data:!(e.dragging||e.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==I.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this._panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");return this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,o=e.layout.height*s;e.setDimensions(a,o),e.setOrigin(0,i),i+=o,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this._initialized&&(this._panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e of Object.values(this.panels))e.layout.interaction.x_linked&&(t.left=Math.max(t.left,e.layout.margin.left),t.right=Math.max(t.right,e.layout.margin.right));let e=0;return this._panel_ids_by_y_index.forEach((s=>{const i=this.panels[s],a=i.layout;if(i.setOrigin(0,e),e+=this.panels[s].layout.height,a.interaction.x_linked){const e=Math.max(t.left-a.margin.left,0)+Math.max(t.right-a.margin.right,0);a.width+=e,a.margin.left=t.left,a.margin.right=t.right,a.cliparea.origin.x=t.left}})),this.setDimensions(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize){I.select(this.container).classed("lz-container-responsive",!0);const t=()=>this.rescaleSVG();if(window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t),"undefined"!=typeof IntersectionObserver){const t={root:document.documentElement,threshold:.9};new IntersectionObserver(((t,e)=>{t.some((t=>t.intersectionRatio>0))&&this.rescaleSVG()}),t).observe(this.container)}const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}if(this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this._mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=_t.call(this),this.loader=pt.call(this),this._panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent._panel_ids_by_y_index.forEach(((t,e)=>{const s=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=I.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent._panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+I.event.dy);const i=t.layout.height-s;this.parent._panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent._panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent._panel_boundaries.selectors.push(s)}));const t=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=I.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+I.event.dx,this.parent._total_height+I.event.dy)})),t.call(e),this.parent._panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent._panel_ids_by_y_index[s]],a=i._getPageOrigin(),o=t.x,n=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${n}px`).style("left",`${o}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&I.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this._panel_boundaries.hide_timeout),this._panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this._panel_boundaries.hide_timeout=setTimeout((()=>{this._panel_boundaries.hide()}),300)})),this.toolbar=new Pt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this._mouse_guide.vertical.attr("x",-1),this._mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=I.mouse(this.svg.node());this._mouse_guide.vertical.attr("x",t[0]),this._mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{const{_interaction:t}=this;if(t.dragging){const e=I.mouse(this.svg.node());I.event&&I.event.preventDefault(),t.dragging.dragged_x=e[0]-t.dragging.start_x,t.dragging.dragged_y=e[1]-t.dragging.start_y,this.panels[t.panel_id].render(),t.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=I.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this._initialized=!0;const a=this.svg.node().getBoundingClientRect(),o=a.width?a.width:this.layout.width,n=a.height?a.height:this._total_height;return this.setDimensions(o,n),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof Ct&&s&&this._canInteract()))return this.stopDrag();const i=I.mouse(this.svg.node());return this._interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){const{_interaction:t}=this;if(!t.dragging)return this;if("object"!=typeof this.panels[t.panel_id])return this._interaction={},this;const e=this.panels[t.panel_id],s=(t,s,i)=>{e._data_layer_ids_by_z_index.forEach((a=>{const o=e.data_layers[a].layout[`${t}_axis`];o.axis===s&&(o.floor=i[0],o.ceiling=i[1],delete o.lower_buffer,delete o.upper_buffer,delete o.min_extent,delete o.ticks)}))};switch(t.dragging.method){case"background":case"x_tick":0!==t.dragging.dragged_x&&(s("x",1,e.x_extent),this.applyState({start:e.x_extent[0],end:e.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==t.dragging.dragged_y){const i=parseInt(t.dragging.method[1]);s("y",i,e[`y${i}_extent`])}}return this._interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function qt(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),o=Math.min(Math.max(e,0),2),n=Math.min(Math.max(a,o),12);let r=`${(t/Math.pow(10,e)).toFixed(n)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Ft(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function Ht(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const o=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(o())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},n=[];for(;i.length>0;)n.push(o());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new K(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return n.map(l).join("")}const Gt=new h;Gt.add("=",((t,e)=>t===e)),Gt.add("!=",((t,e)=>t!=e)),Gt.add("<",((t,e)=>tt<=e)),Gt.add(">",((t,e)=>t>e)),Gt.add(">=",((t,e)=>t>=e)),Gt.add("%",((t,e)=>t%e)),Gt.add("in",((t,e)=>e&&e.includes(t))),Gt.add("match",((t,e)=>t&&t.includes(e)));const Jt=Gt,Zt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Kt=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Wt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Yt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let o=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var o=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(o=i)})),null===o)return a;const t=(+e-s[o-1])/(s[o]-s[o-1]);return isFinite(t)?I.interpolate(i[o-1],i[o])(t):a}};function Qt(t,e){if(void 0===e)return null;const{beta_field:s,stderr_beta_field:i,"+":a=null,"-":o=null}=t;if(!s||!i)throw new Error("effect_direction must specify how to find required 'beta' and 'stderr_beta' fields");const n=e[s],r=e[i];if(void 0!==n)if(void 0!==r){if(n-2*r>0)return a;if(n+2*r<0)return o||null}else{if(n>0)return a;if(n<0)return o}return null}const te=new h;for(let[t,e]of Object.entries(o))te.add(t,e);te.add("if",Zt);const ee=te,se={id:"",type:"",tag:"custom_data_type",namespace:{},data_operations:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class ie{constructor(t,e){this._initialized=!1,this._layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=at(t||{},se),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=ot(this.layout),this.state={},this._state_id=null,this._layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this._tooltips={}),this._global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1},this._data_contract=new Set,this._entities=new Map,this._dependencies=[],this.mutateLayout()}render(){throw new Error("Method must be implemented")}moveForward(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e+1]&&(t[e]=t[e+1],t[e+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e-1]&&(t[e]=t[e-1],t[e-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this._layer_state.extra_fields[i]||(this._layer_state.extra_fields[i]={}),this._layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}mutateLayout(){if(this.parent_plot){const{namespace:t,data_operations:e}=this.layout;this._data_contract=rt(this.layout,Object.keys(t));const[s,i]=this.parent_plot.lzd.config_to_sources(t,e,this);this._entities=s,this._dependencies=i}}_getDataExtent(t,e){return t=t||this.data,I.extent(t,(t=>+new K(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field;let i=t[s];if(void 0===i&&/{{[^{}]*}}/.test(s)&&(i=Ht(s,t,{})),null==i)throw new Error("Unable to generate element ID");const a=i.toString().replace(/\W/g,""),o=`${this.getBaseId()}-${a}`.replace(/([:.[\],])/g,"_");return t[e]=o,o}getElementStatusNodeId(t){return null}getElementById(t){const e=I.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Jt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new K(t):null;if(this.data.length&&this._data_contract.size){const t=new Set(this._data_contract);for(let e of this.data)if(Object.keys(e).forEach((e=>t.delete(e))),!t.size)break;t.size&&console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...t]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`)}return this.data.forEach(((a,o)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(p+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=_<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-_,0),e=Math.max(c.width/2+_-u,0);y=h.x+_-c.width/2-e+t,b=h.x+_-y-7,"top"===w?(g=h.y+p-(v+c.height+8),f="down",m=c.height-1):(g=h.y+p+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+_+x+8,f="left",b=-8):(y=h.x+_-c.width-x-8,f="right",b=c.width-1),p-c.height/2<=0?(g=h.y+p-10.5-6,m=6):p+c.height/2>=d?(g=h.y+p+7+6-c.height,m=c.height-14-6):(g=h.y+p-c.height/2,m=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${f}`).style("left",`${b}px`).style("top",`${m}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:o}=t,n=Jt.get(i),r=this.getElementAnnotation(e);n(s?new K(s).resolve(e,r):e,o)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this._layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;D.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this._state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this._state_id]=t),this._layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this._tooltips[e])return this._tooltips[e]={data:t,arrow:null,selector:I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this._layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this._tooltips[e].selector.html(""),this._tooltips[e].arrow=null,this.layout.tooltip.html&&this._tooltips[e].selector.html(Ht(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this._tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this._tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this._tooltips[s]&&("object"==typeof this._tooltips[s].selector&&this._tooltips[s].selector.remove(),delete this._tooltips[s]),!e){this._layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this._tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this._tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this._tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this._tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){const s=this.layout.tooltip;if("object"!=typeof s)return this;const i=this.getElementId(t),a=(t,e,s)=>{let i=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",i=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let o;for(let n in e)o=a(t,e[n],n),null===i?i=o:"and"===s?i=i&&o:"or"===s&&(i=i||o)}}return i};let o={};"string"==typeof s.show?o={and:[s.show]}:"object"==typeof s.show&&(o=s.show);let n={};"string"==typeof s.hide?n={and:[s.hide]}:"object"==typeof s.hide&&(n=s.hide);const r=this._layer_state;var l={};D.adjectives.forEach((t=>{const e=`un${t}`;l[t]=r.status_flags[t].has(i),l[e]=!l[t]}));const h=a(l,o),c=a(l,n),d=r.status_flags.has_tooltip.has(i);return!h||!e&&!d||c?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),I.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const o=this.getElementStatusNodeId(e);null!==o&&I.select(`#${o}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const n=!this._layer_state.status_flags[t].has(a);s&&n&&this._layer_state.status_flags[t].add(a),s||n||this._layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,n),n&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!n&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!n&&s||this.parent.emit("match_requested",{value:new K(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!D.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this._layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this._layer_state.status_flags[t]=new Set}return this._global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||I.select(I.event.target).datum(),s===!!I.event.ctrlKey&&i===!!I.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a._layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=Ht(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this._layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e._state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this._entities,this._dependencies).then((t=>{this.data=t,this.applyDataMethods(),this._initialized=!0,this.parent.emit("data_from_layer",{layer:this.getBaseId(),content:ot(t)},!0)}))}}D.verbs.forEach(((t,e)=>{const s=D.adjectives[e],i=`un${t}`;ie.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},ie.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},ie.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},ie.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const ae={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class oe extends ie{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");at(t,ae),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],o=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+o)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const ne={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class re extends ie{constructor(t){if(at(t,ne),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions.length&&t.namespace&&Object.keys(t.namespace).length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>I.ascending(t[s],e[s])||I.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,o){const n=a[a.length-1]||t;if(t[s]===n[s]&&t[i]<=n[e]){const s=Math.min(n[i],t[i]),o=Math.max(n[e],t[e]);t=Object.assign({},n,t,{[i]:s,[e]:o}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const le={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class he extends ie{constructor(t){t=at(t,le),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function o(t){const a=t[e.x_axis.field1],o=t[e.x_axis.field2],n=(a+o)/2,r=[[s(a),i(0)],[s(n),i(t[e.y_axis.field])],[s(o),i(0)]];return I.line().x((t=>t[0])).y((t=>t[1])).curve(I.curveNatural)(r)}const n=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(gt,e.style),n.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(n).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>o(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>o(t))),r.exit().remove(),n.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],o=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:o(t.data[s.y_axis.field]),y_max:o(0)}}}const ce={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:12,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class de extends ie{constructor(t){t=at(t,ce),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=I.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const o=I.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,o.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(o).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),o.exit().remove();const n=I.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));n.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(n).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),n.exit().remove();const r=I.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=I.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=I.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const ue={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5,tooltip:null};class _e extends ie{constructor(t){if((t=at(t,ue)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const o=t.x_scale,n=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?I.area().x((t=>+o(t[e]))).y0(+n(0)).y1((t=>+n(t[s]))):I.line().x((t=>+o(t[e]))).y((t=>+n(t[s]))).curve(I[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(gt,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!D.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;void 0===e&&(e=!0),this._global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this._global_statuses).forEach((t=>{this._global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const pe={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class ge extends ie{constructor(t){t=at(t,pe),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments)}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],o=I.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const o=+t[e](s.y);return isNaN(o)?a[i]:o}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",o).call(gt,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=I.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ye={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class fe extends ie{constructor(t){(t=at(t,ye)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),o=Math.sqrt(a/Math.PI);return{x_min:e-o,x_max:e+o,y_min:i-o,y_max:i+o}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,o=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,n=(t,a)=>{const o=+t.attr("x"),n=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",o-n),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",o+n),i&&a.attr("x2",r+l))};t._label_texts.each((function(e,a){const r=I.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>o){const e=i?I.select(t._label_lines.nodes()[a]):null;n(r,e)}})),t._label_texts.each((function(e,o){const r=I.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?I.select(t._label_lines.nodes()[o]):null;t._label_texts.each((function(){const t=I.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(n(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=n.attr("y"),c=.5*(r.topp?(g=d-+o,d=+o,u-=g):u+l.height/2>p&&(g=u-+h,u=+h,d-=g),a.attr("y",d),n.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t._label_texts.nodes();t._label_lines.attr("y2",((t,s)=>I.select(e[s]).attr("y")))}this._label_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let o=this._applyFilters();if(o.forEach((t=>{let o=e(t[this.layout.x_axis.field]),n=s(t[this.layout.y_axis.field]);isNaN(o)&&(o=-1e3),isNaN(n)&&(n=-1e3),t[i]=o,t[a]=n})),this.layout.coalesce.active&&o.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:n,x_gap:r,y_gap:l}=this.layout.coalesce;o=function(t,e,s,i,a,o,n){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function _(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function p(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],f=g>=e&&g<=s&&y>=a&&y<=o;t.lz_is_match||!f?(_(),r.push(t)):null===c?p(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=n?u.push(t):(_(),p(g,y,t))})),_(),r}(o,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(n)?s(+n):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=o.filter(t)}else e=o;this._label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const n=`lz-data_layer-${this.layout.type}-label`,r=this._label_groups.enter().append("g").attr("class",n);this._label_texts&&this._label_texts.remove(),this._label_texts=this._label_groups.merge(r).append("text").text((e=>Ht(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(gt,t.layout.label.style||{}),t.layout.label.lines&&(this._label_lines&&this._label_lines.remove(),this._label_lines=this._label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(gt,t.layout.label.lines.style||{})),this._label_groups.exit().remove()}else this._label_texts&&this._label_texts.remove(),this._label_lines&&this._label_lines.remove(),this._label_groups&&this._label_groups.remove();const n=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(o,(t=>t[this.layout.id_field])),r=I.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>nt(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;n.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(n).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),n.exit().remove(),this.layout.label&&(this.flip_labels(),this._label_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=I.select(I.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class me extends fe{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],o="string"==typeof i?i.toLowerCase():i,n="string"==typeof a?a.toLowerCase():a;return o===n?0:o{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],o=i[e],n=s[a]||[o,o];s[a]=[Math.min(n[0],o),Math.max(n[1],o)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const n=i[t];let r;switch(s){case"left":r=n[0];break;case"center":const t=n[1]-n[0];r=n[0]+(0!==t?t:n[0])/2;break;case"right":r=n[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const be=new c;for(let[t,e]of Object.entries(n))be.add(t,e);const xe=be,ve=7.301,we={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{assoc:variant|htmlescape}}
    \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
    \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
    '},$e=function(){const t=ot(we);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),ze={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

    {{gene_name|htmlescape}}

    Gene ID: {{gene_id|htmlescape}}
    Transcript ID: {{transcript_id|htmlescape}}
    {{#if pLI}}
    ConstraintExpected variantsObserved variantsConst. Metric
    Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
    o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
    Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
    o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
    pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
    o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

    {{/if}}More data on gnomAD'},ke={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{catalog:variant|htmlescape}}
    Catalog entries: {{n_catalog_matches|htmlescape}}
    Top Trait: {{catalog:trait|htmlescape}}
    Top P Value: {{catalog:log_pvalue|logtoscinotation}}
    More: GWAS catalog / dbSNP'},Ee={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
    {{access:start1|htmlescape}}-{{access:end1|htmlescape}}
    Promoter
    {{access:start2|htmlescape}}-{{access:end2|htmlescape}}
    {{#if access:target}}Target: {{access:target|htmlescape}}
    {{/if}}Score: {{access:score|htmlescape}}"},Me={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:ve},Se={id:"recombrate",namespace:{recomb:"recomb"},data_operations:[{type:"fetch",from:["recomb"]}],type:"line",tag:"recombination",z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"recomb:position"},y_axis:{axis:2,field:"recomb:recomb_rate",floor:0,ceiling:100}},Ne={namespace:{assoc:"assoc",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)"]},{type:"left_match",name:"assoc_plus_ld",requires:["assoc","ld"],params:["assoc:position","ld:position2"]}],id:"associationpvalues",type:"scatter",tag:"association",id_field:"assoc:variant",coalesce:{active:!0},point_shape:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"diamond"}},{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"assoc:beta",stderr_beta_field:"assoc:se"}},"circle"],point_size:{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:80,else:40}},color:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"#9632b8"}},{scale_function:"numerical_bin",field:"ld:correlation",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{shape:"diamond",color:"#9632b8",size:40,label:"LD Ref Var",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(219, 61, 17)",size:40,label:"1.0 > r² ≥ 0.8",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(248, 195, 42)",size:40,label:"0.8 > r² ≥ 0.6",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(110, 254, 104)",size:40,label:"0.6 > r² ≥ 0.4",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(38, 188, 225)",size:40,label:"0.4 > r² ≥ 0.2",class:"lz-data_layer-scatter"},{shape:"circle",color:"rgb(70, 54, 153)",size:40,label:"0.2 > r² ≥ 0.0",class:"lz-data_layer-scatter"},{shape:"circle",color:"#AAAAAA",size:40,label:"no r² data",class:"lz-data_layer-scatter"}],label:null,z_index:2,x_axis:{field:"assoc:position"},y_axis:{axis:1,field:"assoc:log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:ot(we)},Ae={id:"coaccessibility",type:"arcs",tag:"coaccessibility",namespace:{access:"access"},data_operations:[{type:"fetch",from:["access"]}],match:{send:"access:target",receive:"access:target"},id_field:"{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}",filters:[{field:"access:score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"access:start1",field2:"access:start2"},y_axis:{axis:1,field:"access:score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:ot(Ee)},Oe=function(){let t=ot(Ne);return t=at({id:"associationpvaluescatalog",fill_opacity:.7},t),t.data_operations.push({type:"assoc_to_gwas_catalog",name:"assoc_catalog",requires:["assoc_plus_ld","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}),t.tooltip.html+='{{#if catalog:rsid}}
    See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t}(),Te={id:"phewaspvalues",type:"category_scatter",tag:"phewas",namespace:{phewas:"phewas"},data_operations:[{type:"fetch",from:["phewas"]}],point_shape:[{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"phewas:beta",stderr_beta_field:"phewas:se"}},"circle"],point_size:70,tooltip_positioning:"vertical",id_field:"{{phewas:trait_group}}_{{phewas:trait_label}}",x_axis:{field:"lz_auto_x",category_field:"phewas:trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"phewas:log_pvalue",floor:0,upper_buffer:.15},color:[{field:"phewas:trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Trait: {{phewas:trait_label|htmlescape}}
    \nTrait Category: {{phewas:trait_group|htmlescape}}
    \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
    β: {{phewas:beta|scinotation|htmlescape}}
    {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}"},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{phewas:trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"phewas:log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},Le={namespace:{gene:"gene",constraint:"constraint"},data_operations:[{type:"fetch",from:["gene","constraint(gene)"]},{name:"gene_constraint",type:"genes_to_gnomad_constraint",requires:["gene","constraint"]}],id:"genes",type:"genes",tag:"genes",id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:ot(ze)},je=at({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},ot(Le)),Pe={namespace:{assoc:"assoc",catalog:"catalog"},data_operations:[{type:"fetch",from:["assoc","catalog"]},{type:"assoc_to_gwas_catalog",name:"assoc_plus_ld",requires:["assoc","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}],id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:"#0000CC",filters:[{field:"catalog:rsid",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:ot(ke),tooltip_positioning:"top"},Re={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},Ie={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},De={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},Ce={widgets:[{type:"title",title:"LocusZoom",subtitle:`v${l}`,position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Be=function(){const t=ot(Ce);return t.widgets.push(ot(Re)),t}(),Ue=function(){const t=ot(Ce);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),qe={id:"association",tag:"association",min_height:200,height:225,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=ot(De);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:28},y2:{label:"Recombination Rate (cM/Mb)",label_offset:40}},legend:{orientation:"vertical",origin:{x:55,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[ot(Me),ot(Se),ot(Ne)]},Fe={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",toolbar:ot(De),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:28,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[ot(Ae)]},He=function(){let t=ot(qe);return t=at({id:"associationcatalog"},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{catalog:trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"catalog:trait",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve},{field:"ld:correlation",operator:">",value:.4}],style:{"font-size":"10px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[ot(Me),ot(Se),ot(Oe)],t}(),Ge={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:50,bottom:20,left:50},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=ot(De);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},ot(Ie)),t}(),data_layers:[ot(je)]},Je={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:50,bottom:120,left:50},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:28}},data_layers:[ot(Me),ot(Te)]},Ze={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:50,bottom:10,left:50},inner_border:"rgb(210, 210, 210)",toolbar:ot(De),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[ot(Pe)]},Ke={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[ot(qe),ot(Ge)]},Ve={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[Ze,He,Ge]},We={width:800,responsive_resize:!0,toolbar:Ce,panels:[ot(Je),at({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"}}},ot(Ge))],mouse_guide:!1},Ye={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:ot(Ce),panels:[ot(Fe),function(){const t=Object.assign({height:270},ot(Ge)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},Xe={standard_association:we,standard_association_with_label:$e,standard_genes:ze,catalog_variant:ke,coaccessibility:Ee},Qe={ldlz2_pop_selector:Re,gene_selector_menu:Ie},ts={standard_panel:De,standard_plot:Ce,standard_association:Be,region_nav_plot:Ue},es={significance:Me,recomb_rate:Se,association_pvalues:Ne,coaccessibility:Ae,association_pvalues_catalog:Oe,phewas_pvalues:Te,genes:Le,genes_filtered:je,annotation_catalog:Pe},ss={association:qe,coaccessibility:Fe,association_catalog:He,genes:Ge,phewas:Je,annotation_catalog:Ze},is={standard_association:Ke,association_catalog:Ve,standard_phewas:We,coaccessibility:Ye};const as=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);const a=s.namespace;i.namespace||delete s.namespace;let o=at(s,i);return a&&(o=it(o,a)),ot(o)}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=ot(s);return"data_layer"===t&&a.namespace&&(a._auto_fields=[...rt(a,Object.keys(a.namespace))].sort()),super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return at(t,e)}renameField(){return lt(...arguments)}mutate_attrs(){return ht(...arguments)}query_attrs(){return ct(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))as.add(t,s,i);const os=as,ns=new h;function rs(t){return(e,s,...i)=>{if(2!==s.length)throw new Error("Join functions must receive exactly two recordsets");return t(...s,...i)}}ns.add("left_match",rs(x)),ns.add("inner_match",rs((function(t,e,s,i){return b("inner",...arguments)}))),ns.add("full_outer_match",rs((function(t,e,s,i){return b("outer",...arguments)}))),ns.add("assoc_to_gwas_catalog",rs((function(t,e,s,i,a){if(!t.length)return t;const o=m(e,i),n=[];for(let t of o.values()){let e,s=0;for(let i of t){const t=i[a];t>=s&&(e=i,s=t)}e.n_catalog_matches=t.length,n.push(e)}return x(t,n,s,i)}))),ns.add("genes_to_gnomad_constraint",rs((function(t,e){return t.forEach((function(t){const s=`_${t.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=e[s]&&e[s].gnomad_constraint;i&&Object.keys(i).forEach((function(e){let s=i[e];void 0===t[e]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),t[e]=s)}))})),t})));const ls=ns;const hs={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return I.select(t).html(""),I.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!I.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Ut(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Ft(s[2]),e=Ft(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Ft(s[2]),end:Ft(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Ft(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=I.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(gt,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||R}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:R,DataLayers:xe,DataFunctions:ls,Layouts:os,MatchFunctions:Jt,ScaleFunctions:ee,TransformationFunctions:Z,Widgets:jt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),R}},cs=[];hs.use=function(t,...e){if(!cs.includes(t)){if(e.unshift(hs),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}cs.push(t)}};const ds=hs})(),LocusZoom=i.default})(); +/*! Locuszoom 0.14.0-beta.2 */ +var LocusZoom;(()=>{var t={483:(t,e,s)=>{"use strict";const i=s(919);t.exports=function(t,...e){if(!t){if(1===e.length&&e[0]instanceof Error)throw e[0];throw new i(e)}}},919:(t,e,s)=>{"use strict";const i=s(820);t.exports=class extends Error{constructor(t){super(t.filter((t=>""!==t)).map((t=>"string"==typeof t?t:t instanceof Error?t.message:i(t))).join(" ")||"Unknown error"),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e.assert)}}},820:t=>{"use strict";t.exports=function(...t){try{return JSON.stringify.apply(null,t)}catch(t){return"[Cannot display object: "+t.message+"]"}}},681:(t,e,s)=>{"use strict";const i=s(483),a={};e.B=class{constructor(){this._items=[],this.nodes=[]}add(t,e){const s=[].concat((e=e||{}).before||[]),a=[].concat(e.after||[]),n=e.group||"?",o=e.sort||0;i(!s.includes(n),`Item cannot come before itself: ${n}`),i(!s.includes("?"),"Item cannot come before unassociated items"),i(!a.includes(n),`Item cannot come after itself: ${n}`),i(!a.includes("?"),"Item cannot come after unassociated items"),Array.isArray(t)||(t=[t]);for(const e of t){const t={seq:this._items.length,sort:o,before:s,after:a,group:n,node:e};this._items.push(t)}if(!e.manual){const t=this._sort();i(t,"item","?"!==n?`added into group ${n}`:"","created a dependencies error")}return this.nodes}merge(t){Array.isArray(t)||(t=[t]);for(const e of t)if(e)for(const t of e._items)this._items.push(Object.assign({},t));this._items.sort(a.mergeSort);for(let t=0;tt.sort===e.sort?0:t.sort{function e(t){if("string"==typeof t.source.flags)return t.source.flags;var e=[];return t.global&&e.push("g"),t.ignoreCase&&e.push("i"),t.multiline&&e.push("m"),t.sticky&&e.push("y"),t.unicode&&e.push("u"),e.join("")}t.exports=function t(s){if("function"==typeof s)return s;var i=Array.isArray(s)?[]:{};for(var a in s){var n=s[a],o={}.toString.call(n).slice(8,-1);i[a]="Array"==o||"Object"==o?t(n):"Date"==o?new Date(n.getTime()):"RegExp"==o?RegExp(n.source,e(n)):n}return i}}},e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={exports:{}};return t[i](a,a.exports,s),a.exports}s.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return s.d(e,{a:e}),e},s.d=(t,e)=>{for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),s.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";s.d(i,{default:()=>ds});var t={};s.r(t),s.d(t,{AssociationLZ:()=>M,BaseAdapter:()=>$,BaseApiAdapter:()=>z,BaseLZAdapter:()=>k,BaseUMAdapter:()=>E,GeneConstraintLZ:()=>A,GeneLZ:()=>N,GwasCatalogLZ:()=>S,LDServer:()=>O,PheWASLZ:()=>j,RecombLZ:()=>T,StaticSource:()=>L});var e={};s.r(e),s.d(e,{htmlescape:()=>F,is_numeric:()=>H,log10:()=>D,logtoscinotation:()=>U,neglog10:()=>B,scinotation:()=>q,urlencode:()=>G});var a={};s.r(a),s.d(a,{BaseWidget:()=>yt,_Button:()=>ft,display_options:()=>Ot,download:()=>vt,download_png:()=>wt,filter_field:()=>xt,menu:()=>St,move_panel_down:()=>kt,move_panel_up:()=>zt,region_scale:()=>bt,remove_panel:()=>$t,resize_to_data:()=>Nt,set_state:()=>Tt,shift_region:()=>Et,title:()=>mt,toggle_legend:()=>At,zoom_region:()=>Mt});var n={};s.r(n),s.d(n,{categorical_bin:()=>Vt,effect_direction:()=>Qt,if_value:()=>Zt,interpolate:()=>Xt,numerical_bin:()=>Kt,ordinal_cycle:()=>Wt,stable_choice:()=>Yt});var o={};s.r(o),s.d(o,{BaseDataLayer:()=>ie,annotation_track:()=>ne,arcs:()=>he,category_scatter:()=>me,genes:()=>de,highlight_regions:()=>re,line:()=>_e,orthogonal_line:()=>ge,scatter:()=>fe});var r={};s.r(r),s.d(r,{data_layer:()=>es,panel:()=>ss,plot:()=>is,toolbar:()=>ts,toolbar_widgets:()=>Qe,tooltip:()=>Xe});const l="0.14.0-beta.2";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}class d{constructor(t,e,s={},i=null,a=null){this.key=t,this.value=e,this.metadata=s,this.prev=i,this.next=a}}class u{constructor(t=3){if(this._max_size=t,this._cur_size=0,this._store=new Map,this._head=null,this._tail=null,null===t||t<0)throw new Error('Cache "max_size" must be >= 0')}has(t){return this._store.has(t)}get(t){const e=this._store.get(t);return e?(this._head!==e&&this.add(t,e.value),e.value):null}add(t,e,s={}){if(0===this._max_size)return;const i=this._store.get(t);i&&this._remove(i);const a=new d(t,e,s,null,this._head);if(this._head?this._head.prev=a:this._tail=a,this._head=a,this._store.set(t,a),this._max_size>=0&&this._cur_size>=this._max_size){const t=this._tail;this._tail=this._tail.prev,this._remove(t)}this._cur_size+=1}clear(){this._head=null,this._tail=null,this._cur_size=0,this._store=new Map}remove(t){const e=this._store.get(t);return!!e&&(this._remove(e),!0)}_remove(t){null!==t.prev?t.prev.next=t.next:this._head=t.next,null!==t.next?t.next.prev=t.prev:this._tail=t.prev,this._store.delete(t.key),this._cur_size-=1}find(t){let e=this._head;for(;e;){const s=e.next;if(t(e))return e;e=s}}}var _=s(957),p=s.n(_);function g(t){return"object"!=typeof t?t:p()(t)}var y=s(681);function f(t,e,s,i=!0){if(!s.length)return[];const a=s.map((t=>function(t){const e=/^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(t);if(!e)throw new Error(`Unable to parse dependency specification: ${t}`);let{name_alone:s,name_deps:i,deps:a}=e.groups;return s?[s,[]]:(a=a.split(/\s*,\s*/),[i,a])}(t))),n=new Map(a),o=new y.B;for(let[t,e]of n.entries())try{o.add(t,{after:e,group:t})}catch(e){throw new Error(`Invalid or possible circular dependency specification for: ${t}`)}const r=o.nodes,l=new Map;for(let s of r){const i=e.get(s);if(!i)throw new Error(`Data has been requested from source '${s}', but no matching source was provided`);const a=n.get(s)||[],o=Promise.all(a.map((t=>l.get(t)))).then((e=>{const a=Object.assign({_provider_name:s},t);return i.getData(a,...e)}));l.set(s,o)}return Promise.all([...l.values()]).then((t=>i?t[t.length-1]:t))}function m(t,e){const s=new Map;for(let i of t){const t=i[e];if(void 0===t)throw new Error(`All records must specify a value for the field "${e}"`);if("object"==typeof t)throw new Error("Attempted to group on a field with non-primitive values");let a=s.get(t);a||(a=[],s.set(t,a)),a.push(i)}return s}function b(t,e,s,i,a){const n=m(s,a),o=[];for(let s of e){const e=s[i],a=n.get(e)||[];a.length?o.push(...a.map((t=>Object.assign({},g(t),g(s))))):"inner"!==t&&o.push(g(s))}if("outer"===t){const t=m(e,i);for(let e of s){const s=e[a];(t.get(s)||[]).length||o.push(g(e))}}return o}function x(t,e,s,i){return b("left",...arguments)}const v=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function w(t,e=!1){const s=t&&t.match(v);if(s)return s.slice(1);if(e)return null;throw new Error(`Could not understand marker format for ${t}. Should be of format chr:pos or chr:pos_ref/alt`)}class ${constructor(){throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.')}}class z extends ${}class k extends class extends class{constructor(t={}){this._config=t;const{cache_enabled:e=!0,cache_size:s=3}=t;this._enable_cache=e,this._cache=new u(s)}_buildRequestOptions(t,e){return Object.assign({},t)}_getCacheKey(t){if(this._enable_cache)throw new Error("Method not implemented");return null}_performRequest(t){throw new Error("Not implemented")}_normalizeResponse(t,e){return t}_annotateRecords(t,e){return t}_postProcessResponse(t,e){return t}getData(t={},...e){t=this._buildRequestOptions(t,...e);const s=this._getCacheKey(t);let i;return this._enable_cache&&this._cache.has(s)?i=this._cache.get(s):(i=Promise.resolve(this._performRequest(t)).then((e=>this._normalizeResponse(e,t))),this._cache.add(s,i,t._cache_meta),i.catch((t=>this._cache.remove(s)))),i.then((t=>g(t))).then((e=>this._annotateRecords(e,t))).then((e=>this._postProcessResponse(e,t)))}}{constructor(t={}){super(t),this._url=t.url}_getCacheKey(t){return this._getURL(t)}_getURL(t){return this._url}_performRequest(t){const e=this._getURL(t);if(!this._url)throw new Error('Web based resources must specify a resource URL as option "url"');return fetch(e).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}_normalizeResponse(t,e){return"string"==typeof t?JSON.parse(t):t}}{constructor(t={}){t.params&&(console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'),Object.assign(t,t.params||{}),delete t.params),super(t);const{prefix_namespace:e=!0,limit_fields:s}=t;this._prefix_namespace=e,this._limit_fields=!!s&&new Set(s)}_getCacheKey(t){let{chr:e,start:s,end:i}=t;const a=this._cache.find((({metadata:t})=>e===t.chr&&s>=t.start&&i<=t.end));return a&&({chr:e,start:s,end:i}=a.metadata),t._cache_meta={chr:e,start:s,end:i},`${e}_${s}_${i}`}_postProcessResponse(t,e){if(!this._prefix_namespace||!Array.isArray(t))return t;const{_limit_fields:s}=this,{_provider_name:i}=e;return t.map((t=>Object.entries(t).reduce(((t,[e,a])=>(s&&!s.has(e)||(t[`${i}:${e}`]=a),t)),{})))}_findPrefixedKey(t,e){const s=new RegExp(`:${e}$`),i=Object.keys(t).find((t=>s.test(t)));if(!i)throw new Error(`Could not locate the required key name: ${e} in dependent data`);return i}}class E extends k{constructor(t={}){super(t),this._genome_build=t.genome_build||t.build}_validateBuildSource(t,e){if(t&&e||!t&&!e)throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`)}_normalizeResponse(t,e){let s=super._normalizeResponse(...arguments);if(s=s.data||s,Array.isArray(s))return s;const i=Object.keys(s),a=s[i[0]].length;if(!i.every((function(t){return s[t].length===a})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const n=[],o=Object.keys(s);for(let t=0;t25||"GRCh38"===s)return Promise.resolve([]);e=`{${e.join(" ")} }`;const i=this._getURL(t),a=JSON.stringify({query:e});return fetch(i,{method:"POST",body:a,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}_normalizeResponse(t){if("string"!=typeof t)return t;return JSON.parse(t).data}}class O extends E{constructor(t){t.limit_fields||(t.limit_fields=["variant2","position2","correlation"]),super(t)}__find_ld_refvar(t,e){const s=this._findPrefixedKey(e[0],"variant"),i=this._findPrefixedKey(e[0],"log_pvalue");let a,n={};if(t.ldrefvar)a=t.ldrefvar,n=e.find((t=>t[s]===a))||{};else{let t=0;for(let o of e){const{[s]:e,[i]:r}=o;r>t&&(t=r,a=e,n=o)}}n.lz_is_ld_refvar=!0;const o=w(a,!0);if(!o)throw new Error("Could not request LD for a missing or incomplete marker format");const[r,l,h,c]=o;a=`${r}:${l}`,h&&c&&(a+=`_${h}/${c}`);const d=+l;return d&&t.ldrefvar&&t.chr&&(r!==t.chr||dt.end)?(t.ldrefvar=null,this.__find_ld_refvar(t,e)):a}_buildRequestOptions(t,e){if(!e)throw new Error("LD request must depend on association data");const s=super._buildRequestOptions(...arguments);if(!e.length)return s._skip_request=!0,s;s.ld_refvar=this.__find_ld_refvar(t,e);const i=t.genome_build||this._config.build||"GRCh37";let a=t.ld_source||this._config.source||"1000G";const n=t.ld_pop||this._config.population||"ALL";return"1000G"===a&&"GRCh38"===i&&(a="1000G-FRZ09"),this._validateBuildSource(i,null),Object.assign({},s,{genome_build:i,ld_source:a,ld_population:n})}_getURL(t){const e=this._config.method||"rsquare",{chr:s,start:i,end:a,ld_refvar:n,genome_build:o,ld_source:r,ld_population:l}=t;return[super._getURL(t),"genome_builds/",o,"/references/",r,"/populations/",l,"/variants","?correlation=",e,"&variant=",encodeURIComponent(n),"&chrom=",encodeURIComponent(s),"&start=",encodeURIComponent(i),"&stop=",encodeURIComponent(a)].join("")}_getCacheKey(t){const e=super._getCacheKey(t),{ld_refvar:s,ld_source:i,ld_population:a}=t;return`${e}_${s}_${i}_${a}`}_performRequest(t){if(t._skip_request)return Promise.resolve([]);const e=this._getURL(t);let s={data:{}},i=function(t){return fetch(t).then().then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){s.data[e]=(s.data[e]||[]).concat(t.data[e])})),t.next?i(t.next):s}))};return i(e)}}class T extends E{constructor(t){t.limit_fields||(t.limit_fields=["position","recomb_rate"]),super(t)}_getURL(t){const e=t.genome_build||this._config.build;let s=this._config.source;this._validateBuildSource(e,s);const i=e?`&build=${e}`:` and id in ${s}`;return`${super._getURL(t)}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${i}`}}class L extends k{constructor(t={}){super(...arguments);const{data:e}=t;if(!e||Array.isArray(t))throw new Error("'StaticSource' must provide data as required option 'config.data'");this._data=e}_performRequest(t){return Promise.resolve(this._data)}}class j extends E{_getURL(t){const e=(t.genome_build?[t.genome_build]:null)||this._config.build;if(!e||!Array.isArray(e)||!e.length)throw new Error(["Adapter",this.constructor.name,"requires that you specify array of one or more desired genome build names"].join(" "));return[super._getURL(t),"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",e.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}_getCacheKey(t){return this._getURL(t)}}const P=new c;for(let[e,s]of Object.entries(t))P.add(e,s);P.add("StaticJSON",L),P.add("LDLZ2",O);const R=P,I=d3,C={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function D(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function B(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function U(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function q(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function F(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function H(t){return"number"==typeof t}function G(t){return encodeURIComponent(t)}const J=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,s]of Object.entries(e))J.add(t,s);const Z=J;class K{constructor(t){if(!/^(?:\w+:\w+|^\w+)(?:\|\w+)*$/.test(t))throw new Error(`Invalid field specifier: '${t}'`);const[e,...s]=t.split("|");this.full_name=t,this.field_name=e,this.transformations=s.map((t=>Z.get(t)))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[this.field_name]?s=t[this.field_name]:e&&void 0!==e[this.field_name]&&(s=e[this.field_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const V=/^(\*|[\w]+)/,W=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function Y(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=V.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=V.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=W.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function X(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function Q(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const n=t[s.attr];1===e.length?void 0!==n&&a.push([s.attr]):a.push(...Q(n,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...Q(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...Q(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[n,o]of Object.entries(t))a.push(...Q(o,e).map((t=>[n].concat(t)))),"*"===s.attr&&a.push(...Q(o,i).map((t=>[n].concat(t))))}}else if(s.attrs)for(let[e,n]of Object.entries(t)){const[t,o,r]=X(n,s.attrs);r===s.value&&a.push(...Q(n,i).map((t=>[e].concat(t))))}const n=(o=a,r=JSON.stringify,[...new Map(o.map((t=>[r(t),t]))).values()]);var o,r;return n.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),n}function tt(t,e){const s=function(t,e){let s=[];for(let i of Q(t,e))s.push(X(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=Y(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const et=Math.sqrt(3),st={draw(t,e){const s=-Math.sqrt(e/(3*et));t.moveTo(0,2*-s),t.lineTo(-et*s,s),t.lineTo(et*s,s),t.closePath()}};function it(t,e){if(e=e||{},!t||"object"!=typeof t||"object"!=typeof e)throw new Error("Layout and shared namespaces must be provided as objects");for(let[s,i]of Object.entries(t))"namespace"===s?Object.keys(i).forEach((t=>{const s=e[t];s&&(i[t]=s)})):null!==i&&"object"==typeof i&&(t[s]=it(i,e));return t}function at(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=at(t[s],e[s])):t[s]=nt(e[s])}return t}function nt(t){return JSON.parse(JSON.stringify(t))}function ot(t){if(!t)return null;if("triangledown"===t)return st;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return I[e]||null}function rt(t,e,s=null){const i=new Set;if(!s){if(!e.length)return i;const t=e.join("|");s=new RegExp(`(?:{{)?(?:#if *)?((?:${t}):\\w+)`,"g")}for(const a of Object.values(t)){const t=typeof a;let n=[];if("string"===t){let t;for(;null!==(t=s.exec(a));)n.push(t[1])}else{if(null===a||"object"!==t)continue;n=rt(a,e,s)}for(let t of n)i.add(t)}return i}function lt(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>lt(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,n)=>(a[n]=lt(t[n],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const n=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(n,s)}}function ht(t,e,s){return function(t,e,s){return tt(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function ct(t,e){return function(t,e){return tt(t,e).map((t=>t[2]))}(t,e)}class dt{constructor(t,e,s){this._callable=ls.get(t),this._initiator=e,this._params=s||[]}getData(t,...e){const s={plot_state:t,data_layer:this._initiator};return Promise.resolve(this._callable(s,e,...this._params))}}const ut=class{constructor(t){this._sources=t}config_to_sources(t={},e=[],s){const i=new Map,a=Object.keys(t);let n=e.find((t=>"fetch"===t.type));n||(n={type:"fetch",from:a},e.unshift(n));const o=/^\w+$/;for(let[e,s]of Object.entries(t)){if(!o.test(e))throw new Error(`Invalid namespace name: '${e}'. Must contain only alphanumeric characters`);const t=this._sources.get(s);if(!t)throw new Error(`A data layer has requested an item not found in DataSources: data type '${e}' from ${s}`);i.set(e,t),n.from.find((t=>t.split("(")[0]===e))||n.from.push(e)}let r=Array.from(n.from);for(let t of e){let{type:e,name:a,requires:n,params:o}=t;if("fetch"!==e){let l=0;if(a||(a=t.name=`join${l}`,l+=1),i.has(a))throw new Error(`Configuration error: within a layer, join name '${a}' must be unique`);n.forEach((t=>{if(!i.has(t))throw new Error(`Data operation cannot operate on unknown provider '${t}'`)}));const h=new dt(e,s,o);i.set(a,h),r.push(`${a}(${n.join(", ")})`)}}return[i,r]}getData(t,e,s){return s.length?f(t,e,s,!0):Promise.resolve([])}};function _t(){return{showing:!1,selector:null,content_selector:null,hide_delay:null,show:(t,e)=>(this.curtain.showing||(this.curtain.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&>(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function pt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function gt(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class yt{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&>(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class ft{constructor(t){if(!(t instanceof yt))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),n=this.menu.outer_selector.node().getBoundingClientRect(),o=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-n.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-n.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),_=Math.min(o+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${_}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(gt,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class mt extends yt{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class bt extends yt{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(qt(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&>(this.selector,this.layout.style),this}}class xt extends yt{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class vt extends yt{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=I.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+I.select(this).attr("dy").substring(-2).slice(0,-2);I.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class wt extends vt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((n,o)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{n(URL.createObjectURL(t))}))},r.src=t}))}))}}class $t extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),I.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),I.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class zt extends yt{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class kt extends yt{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot._panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class Et extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${qt(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class Mt extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class St extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class Nt extends yt{constructor(t){super(...arguments)}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class At extends yt{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new ft(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class Ot extends yt{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,n={};s.forEach((t=>{const e=a[t];void 0!==e&&(n[t]=nt(e))})),this._selected_item="default",this.button=new ft(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,o=(a,o,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==o[t];i.layout[t]=e?o[t]:n[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return o(r,n,"default"),a.options.forEach(((t,e)=>o(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class Tt extends yt{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new ft(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,n)=>{const o=s.append("tr"),r=`${e}${n}`;o.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",n).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),o.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const Lt=new c;for(let[t,e]of Object.entries(a))Lt.add(t,e);const jt=Lt;class Pt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{this.addWidget(t)})),"panel"===this.type&&I.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}addWidget(t){try{const e=jt.create(t.type,t,this);return this.widgets.push(e),e}catch(t){console.warn("Failed to create widget"),console.error(t)}}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot._panel_boundaries.dragging||this.parent_plot._interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=I.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=I.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const Rt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:14,hidden:!1};class It{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=at(this.parent.layout.legend||{},Rt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((a=>{const n=this.parent.data_layers[a].layout.legend;Array.isArray(n)&&n.forEach((a=>{const n=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),o=+a.label_size||+this.layout.label_size;let r=0,l=o/2+t/2;i=Math.max(i,o+t);const h=a.shape||"",c=ot(h);if("line"===h){const e=+a.length||16,s=o/4+t/2;n.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(gt,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;n.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(gt,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if("ribbon"===h){const e=+a.width||25,s=+a.height||e,i="horizontal"===(a.orientation||"vertical");let o=a.color_stops;const h=n.append("g"),c=h.append("g"),d=h.append("g");let u=0;if(a.tick_labels){let t;t=i?[0,e*o.length-1]:[s*o.length-1,0];const n=I.scaleLinear().domain(I.extent(a.tick_labels)).range(t),r=(i?I.axisTop:I.axisRight)(n).tickSize(3).tickValues(a.tick_labels).tickFormat((t=>t));d.call(r).attr("class","lz-axis"),u=d.node().getBoundingClientRect().height}i?(d.attr("transform",`translate(0, ${u})`),c.attr("transform",`translate(0, ${u})`)):(h.attr("transform","translate(5, 0)"),d.attr("transform",`translate(${e}, 0)`)),i||(o=o.slice(),o.reverse());for(let t=0;tt&&a>this.parent.parent.layout.width&&(s+=i,e=t,n.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(n)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const Ct={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class Dt{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"!=typeof t.id||!t.id)throw new Error('Panel layouts must specify "id"');if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`);this.id=t.id,this._initialized=!1,this._layout_idx=null,this.svg={},this.layout=at(t||{},Ct),this.parent?(this.state=this.parent.state,this._state_id=this.id,this.state[this._state_id]=this.state[this._state_id]||{}):(this.state=null,this._state_id=null),this.data_layers={},this._data_layer_ids_by_z_index=[],this._data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this._zoom_timeout=null,this._event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this._event_hooks[t]&&this._event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=at(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(gt,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id '${t.id}'; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=xe.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this._data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this._data_layer_ids_by_z_index.length+e.layout.z_index,0)),this._data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this._data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id]._layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){const e=this.data_layers[t];if(!e)throw new Error(`Unable to remove data layer, ID not found: ${t}`);return e.destroyAllTooltips(),e.svg.container&&e.svg.container.remove(),this.layout.data_layers.splice(e._layout_idx,1),delete this.state[e._state_id],delete this.data_layers[t],this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id]._layout_idx=e})),this}clearSelections(){return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height);const{cliparea:t}=this.layout,{margin:e}=this.layout;this.inner_border.attr("x",e.left).attr("y",e.top).attr("width",this.parent_plot.layout.width-(e.left+e.right)).attr("height",this.layout.height-(e.top+e.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const s=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),n=Math.pow(10,e);return t===1/0&&(t=n),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,n),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},i={},a=this.layout.axes;if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};a.x.range&&(t.start=a.x.range.start||t.start,t.end=a.x.range.end||t.end),i.x=[t.start,t.end],i.x_shifted=[t.start,t.end]}if(this.y1_extent){const e={start:t.height,end:0};a.y1.range&&(e.start=a.y1.range.start||e.start,e.end=a.y1.range.end||e.end),i.y1=[e.start,e.end],i.y1_shifted=[e.start,e.end]}if(this.y2_extent){const e={start:t.height,end:0};a.y2.range&&(e.start=a.y2.range.start||e.start,e.end=a.y2.range.end||e.end),i.y2=[e.start,e.end],i.y2_shifted=[e.start,e.end]}let{_interaction:n}=this.parent;const o=n.dragging;if(n.panel_id&&(n.panel_id===this.id||n.linked_panel_ids.includes(this.id))){let a,r=null;if(n.zooming&&"function"==typeof this.x_scale){const s=Math.abs(this.x_extent[1]-this.x_extent[0]),o=Math.round(this.x_scale.invert(i.x_shifted[1]))-Math.round(this.x_scale.invert(i.x_shifted[0]));let r=n.zooming.scale;const l=Math.floor(o*(1/r));r<1&&!isNaN(this.parent.layout.max_region_scale)?r=1/(Math.min(l,this.parent.layout.max_region_scale)/o):r>1&&!isNaN(this.parent.layout.min_region_scale)&&(r=1/(Math.max(l,this.parent.layout.min_region_scale)/o));const h=Math.floor(s*r);a=n.zooming.center-e.left-this.layout.origin.x;const c=a/t.width,d=Math.max(Math.floor(this.x_scale.invert(i.x_shifted[0])-(h-o)*c),1);i.x_shifted=[this.x_scale(d),this.x_scale(d+h)]}else if(o)switch(o.method){case"background":i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x;break;case"x_tick":I.event&&I.event.shiftKey?(i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x):(a=o.start_x-e.left-this.layout.origin.x,r=s(a/(a+o.dragged_x),3),i.x_shifted[0]=0,i.x_shifted[1]=Math.max(t.width*(1/r),1));break;case"y1_tick":case"y2_tick":{const n=`y${o.method[1]}_shifted`;I.event&&I.event.shiftKey?(i[n][0]=t.height+o.dragged_y,i[n][1]=+o.dragged_y):(a=t.height-(o.start_y-e.top-this.layout.origin.y),r=s(a/(a-o.dragged_y),3),i[n][0]=t.height,i[n][1]=t.height-t.height*(1/r))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(i[t][0]),this[`${t}_scale`].invert(i[t][1])],this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!I.event.shiftKey&&!I.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(I.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=I.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,I.event.wheelDelta||-I.event.detail||-I.event.deltaY));0!==e&&(this.parent._interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),n=this.parent._interaction,n.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this._zoom_timeout&&clearTimeout(this._zoom_timeout),this._zoom_timeout=setTimeout((()=>{this.parent._interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this._initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{const e=this.layout.axes[t];Object.keys(e).length&&!1!==e.render?(e.render=!0,e.label=e.label||null):e.render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){const s=this.layout;return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),s.height=Math.max(Math.round(+e),s.min_height)),s.cliparea.width=Math.max(this.parent_plot.layout.width-(s.margin.left+s.margin.right),0),s.cliparea.height=Math.max(s.height-(s.margin.top+s.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",s.height),this._initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this._initialized&&this.render(),this}setMargin(t,e,s,i){let a;const{cliparea:n,margin:o}=this.layout;return!isNaN(t)&&t>=0&&(o.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(o.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(o.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(o.left=Math.max(Math.round(+i),0)),o.top+o.bottom>this.layout.height&&(a=Math.floor((o.top+o.bottom-this.layout.height)/2),o.top-=a,o.bottom-=a),o.left+o.right>this.parent_plot.layout.width&&(a=Math.floor((o.left+o.right-this.parent_plot.layout.width)/2),o.left-=a,o.right-=a),["top","right","bottom","left"].forEach((t=>{o[t]=Math.max(o[t],0)})),n.width=Math.max(this.parent_plot.layout.width-(o.left+o.right),0),n.height=Math.max(this.layout.height-(o.top+o.bottom),0),n.origin.x=o.left,n.origin.y=o.top,this._initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=_t.call(this),this.loader=pt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new Pt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new It(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this._data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(I.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent._panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){const{parent:t}=this,e=this.layout.y_index;return t._panel_ids_by_y_index[e-1]&&(t._panel_ids_by_y_index[e]=t._panel_ids_by_y_index[e-1],t._panel_ids_by_y_index[e-1]=this.id,t.applyPanelYIndexesToPanelLayouts(),t.positionPanels()),this}moveDown(){const{_panel_ids_by_y_index:t}=this.parent;return t[this.layout.y_index+1]&&(t[this.layout.y_index]=t[this.layout.y_index+1],t[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this._data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this._data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this._data_promises).then((()=>{this._initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=I.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=I.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this._data_layer_ids_by_z_index.reduce(((e,a)=>{const n=s.data_layers[a];return e.concat(n.getTicks(t,i))}),[]).map((t=>{let s={};return s=at(s,e),at(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,n=1.5,o=.5+1.5*n,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(_=parseFloat(_.toFixed(c)));u.push(_),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eqt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=I.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=I.select(this).select("text");s[`${t}_ticks`][i].style&>(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const n=this.layout.axes[t].label||null;return null!==n&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(Ht(n,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof I.select(this).node().focus&&I.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";I.event&&I.event.shiftKey&&(i="move"),I.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){I.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this._data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this._data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;Dt.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Dt.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Bt={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Ut{constructor(t,e,s){this._initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this._panel_ids_by_y_index=[],this._remap_promises=[],this.layout=s,at(this.layout,Bt),this._base_layout=nt(this.layout),this.state=this.layout.state,this.lzd=new ut(e),this._external_listeners=new Map,this._event_hooks={},this._interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this._event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this._event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Dt(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this._panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this._panel_ids_by_y_index.length+e.layout.y_index,0)),this._panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this._panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id]._layout_idx=s,this._initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t]._data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i._layer_state,delete this.layout.state[i._state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){const e=this.panels[t];if(!e)throw new Error(`Unable to remove panel, ID not found: ${t}`);return this._panel_boundaries.hide(),this.clearPanelData(t),e.loader.hide(),e.toolbar.destroy(!0),e.curtain.hide(),e.svg.container&&e.svg.container.remove(),this.layout.panels.splice(e._layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id]._layout_idx=e})),this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this._initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e){const{from_layer:s,namespace:i,data_operations:a,onerror:n}=t,o=n||function(t){console.error("An error occurred while acting on an external callback",t)};if(s){const t=`${this.getBaseId()}.`,i=s.startsWith(t)?s:`${t}${s}`;let a=!1;for(let t of Object.values(this.panels))if(a=Object.values(t.data_layers).some((t=>t.getBaseId()===i)),a)break;if(!a)throw new Error(`Could not subscribe to unknown data layer ${i}`);const n=t=>{if(t.data.layer===i)try{e(t.data.content,this)}catch(t){o(t)}};return this.on("data_from_layer",n),n}if(!i)throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option");const[r,l]=this.lzd.config_to_sources(i,a),h=()=>{try{this.lzd.getData(this.state,r,l).then((t=>e(t,this))).catch(o)}catch(t){o(t)}};return this.on("data_rendered",h),h}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this._remap_promises=[],this.loading_data=!0;for(let t in this.panels)this._remap_promises.push(this.panels[t].reMap());return Promise.all(this._remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e._data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this._initialized=!1,this.svg=null,this.panels=null}mutateLayout(){Object.values(this.panels).forEach((t=>{Object.values(t.data_layers).forEach((t=>t.mutateLayout()))}))}_canInteract(t){t=t||null;const{_interaction:e}=this;return t?(void 0===e.panel_id||e.panel_id===t)&&!this.loading_data:!(e.dragging||e.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==I.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this._panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");return this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,n=e.layout.height*s;e.setDimensions(a,n),e.setOrigin(0,i),i+=n,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this._initialized&&(this._panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e of Object.values(this.panels))e.layout.interaction.x_linked&&(t.left=Math.max(t.left,e.layout.margin.left),t.right=Math.max(t.right,e.layout.margin.right));let e=0;return this._panel_ids_by_y_index.forEach((s=>{const i=this.panels[s],a=i.layout;if(i.setOrigin(0,e),e+=this.panels[s].layout.height,a.interaction.x_linked){const e=Math.max(t.left-a.margin.left,0)+Math.max(t.right-a.margin.right,0);a.width+=e,a.margin.left=t.left,a.margin.right=t.right,a.cliparea.origin.x=t.left}})),this.setDimensions(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize){I.select(this.container).classed("lz-container-responsive",!0);const t=()=>this.rescaleSVG();if(window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t),"undefined"!=typeof IntersectionObserver){const t={root:document.documentElement,threshold:.9};new IntersectionObserver(((t,e)=>{t.some((t=>t.intersectionRatio>0))&&this.rescaleSVG()}),t).observe(this.container)}const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}if(this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this._mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=_t.call(this),this.loader=pt.call(this),this._panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent._panel_ids_by_y_index.forEach(((t,e)=>{const s=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=I.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent._panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+I.event.dy);const i=t.layout.height-s;this.parent._panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent._panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent._panel_boundaries.selectors.push(s)}));const t=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=I.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+I.event.dx,this.parent._total_height+I.event.dy)})),t.call(e),this.parent._panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent._panel_ids_by_y_index[s]],a=i._getPageOrigin(),n=t.x,o=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${o}px`).style("left",`${n}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&I.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this._panel_boundaries.hide_timeout),this._panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this._panel_boundaries.hide_timeout=setTimeout((()=>{this._panel_boundaries.hide()}),300)})),this.toolbar=new Pt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this._mouse_guide.vertical.attr("x",-1),this._mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=I.mouse(this.svg.node());this._mouse_guide.vertical.attr("x",t[0]),this._mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{const{_interaction:t}=this;if(t.dragging){const e=I.mouse(this.svg.node());I.event&&I.event.preventDefault(),t.dragging.dragged_x=e[0]-t.dragging.start_x,t.dragging.dragged_y=e[1]-t.dragging.start_y,this.panels[t.panel_id].render(),t.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=I.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this._initialized=!0;const a=this.svg.node().getBoundingClientRect(),n=a.width?a.width:this.layout.width,o=a.height?a.height:this._total_height;return this.setDimensions(n,o),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof Dt&&s&&this._canInteract()))return this.stopDrag();const i=I.mouse(this.svg.node());return this._interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){const{_interaction:t}=this;if(!t.dragging)return this;if("object"!=typeof this.panels[t.panel_id])return this._interaction={},this;const e=this.panels[t.panel_id],s=(t,s,i)=>{e._data_layer_ids_by_z_index.forEach((a=>{const n=e.data_layers[a].layout[`${t}_axis`];n.axis===s&&(n.floor=i[0],n.ceiling=i[1],delete n.lower_buffer,delete n.upper_buffer,delete n.min_extent,delete n.ticks)}))};switch(t.dragging.method){case"background":case"x_tick":0!==t.dragging.dragged_x&&(s("x",1,e.x_extent),this.applyState({start:e.x_extent[0],end:e.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==t.dragging.dragged_y){const i=parseInt(t.dragging.method[1]);s("y",i,e[`y${i}_extent`])}}return this._interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function qt(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),n=Math.min(Math.max(e,0),2),o=Math.min(Math.max(a,n),12);let r=`${(t/Math.pow(10,e)).toFixed(o)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Ft(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function Ht(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const n=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(n())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},o=[];for(;i.length>0;)o.push(n());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new K(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return o.map(l).join("")}const Gt=new h;Gt.add("=",((t,e)=>t===e)),Gt.add("!=",((t,e)=>t!=e)),Gt.add("<",((t,e)=>tt<=e)),Gt.add(">",((t,e)=>t>e)),Gt.add(">=",((t,e)=>t>=e)),Gt.add("%",((t,e)=>t%e)),Gt.add("in",((t,e)=>e&&e.includes(t))),Gt.add("match",((t,e)=>t&&t.includes(e)));const Jt=Gt,Zt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Kt=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Wt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Yt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let n=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var n=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(n=i)})),null===n)return a;const t=(+e-s[n-1])/(s[n]-s[n-1]);return isFinite(t)?I.interpolate(i[n-1],i[n])(t):a}};function Qt(t,e){if(void 0===e)return null;const{beta_field:s,stderr_beta_field:i,"+":a=null,"-":n=null}=t;if(!s||!i)throw new Error("effect_direction must specify how to find required 'beta' and 'stderr_beta' fields");const o=e[s],r=e[i];if(void 0!==o)if(void 0!==r){if(o-1.96*r>0)return a;if(o+1.96*r<0)return n||null}else{if(o>0)return a;if(o<0)return n}return null}const te=new h;for(let[t,e]of Object.entries(n))te.add(t,e);te.add("if",Zt);const ee=te,se={id:"",type:"",tag:"custom_data_type",namespace:{},data_operations:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class ie{constructor(t,e){this._initialized=!1,this._layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=at(t||{},se),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=nt(this.layout),this.state={},this._state_id=null,this._layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this._tooltips={}),this._global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1},this._data_contract=new Set,this._entities=new Map,this._dependencies=[],this.mutateLayout()}render(){throw new Error("Method must be implemented")}moveForward(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e+1]&&(t[e]=t[e+1],t[e+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e-1]&&(t[e]=t[e-1],t[e-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this._layer_state.extra_fields[i]||(this._layer_state.extra_fields[i]={}),this._layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}mutateLayout(){if(this.parent_plot){const{namespace:t,data_operations:e}=this.layout;this._data_contract=rt(this.layout,Object.keys(t));const[s,i]=this.parent_plot.lzd.config_to_sources(t,e,this);this._entities=s,this._dependencies=i}}_getDataExtent(t,e){return t=t||this.data,I.extent(t,(t=>+new K(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field;let i=t[s];if(void 0===i&&/{{[^{}]*}}/.test(s)&&(i=Ht(s,t,{})),null==i)throw new Error("Unable to generate element ID");const a=i.toString().replace(/\W/g,""),n=`${this.getBaseId()}-${a}`.replace(/([:.[\],])/g,"_");return t[e]=n,n}getElementStatusNodeId(t){return null}getElementById(t){const e=I.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Jt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new K(t):null;if(this.data.length&&this._data_contract.size){const t=new Set(this._data_contract);for(let e of this.data)if(Object.keys(e).forEach((e=>t.delete(e))),!t.size)break;t.size&&console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...t]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`)}return this.data.forEach(((a,n)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(p+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=_<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-_,0),e=Math.max(c.width/2+_-u,0);y=h.x+_-c.width/2-e+t,b=h.x+_-y-7,"top"===w?(g=h.y+p-(v+c.height+8),f="down",m=c.height-1):(g=h.y+p+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+_+x+8,f="left",b=-8):(y=h.x+_-c.width-x-8,f="right",b=c.width-1),p-c.height/2<=0?(g=h.y+p-10.5-6,m=6):p+c.height/2>=d?(g=h.y+p+7+6-c.height,m=c.height-14-6):(g=h.y+p-c.height/2,m=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${f}`).style("left",`${b}px`).style("top",`${m}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:n}=t,o=Jt.get(i),r=this.getElementAnnotation(e);o(s?new K(s).resolve(e,r):e,n)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this._layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;C.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this._state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this._state_id]=t),this._layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this._tooltips[e])return this._tooltips[e]={data:t,arrow:null,selector:I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this._layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this._tooltips[e].selector.html(""),this._tooltips[e].arrow=null,this.layout.tooltip.html&&this._tooltips[e].selector.html(Ht(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this._tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this._tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this._tooltips[s]&&("object"==typeof this._tooltips[s].selector&&this._tooltips[s].selector.remove(),delete this._tooltips[s]),!e){this._layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this._tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this._tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this._tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this._tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){const s=this.layout.tooltip;if("object"!=typeof s)return this;const i=this.getElementId(t),a=(t,e,s)=>{let i=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",i=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let n;for(let o in e)n=a(t,e[o],o),null===i?i=n:"and"===s?i=i&&n:"or"===s&&(i=i||n)}}return i};let n={};"string"==typeof s.show?n={and:[s.show]}:"object"==typeof s.show&&(n=s.show);let o={};"string"==typeof s.hide?o={and:[s.hide]}:"object"==typeof s.hide&&(o=s.hide);const r=this._layer_state;var l={};C.adjectives.forEach((t=>{const e=`un${t}`;l[t]=r.status_flags[t].has(i),l[e]=!l[t]}));const h=a(l,n),c=a(l,o),d=r.status_flags.has_tooltip.has(i);return!h||!e&&!d||c?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),I.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const n=this.getElementStatusNodeId(e);null!==n&&I.select(`#${n}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const o=!this._layer_state.status_flags[t].has(a);s&&o&&this._layer_state.status_flags[t].add(a),s||o||this._layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,o),o&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!o&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!o&&s||this.parent.emit("match_requested",{value:new K(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this._layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this._layer_state.status_flags[t]=new Set}return this._global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||I.select(I.event.target).datum(),s===!!I.event.ctrlKey&&i===!!I.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a._layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=Ht(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this._layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e._state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this._entities,this._dependencies).then((t=>{this.data=t,this.applyDataMethods(),this._initialized=!0,this.parent.emit("data_from_layer",{layer:this.getBaseId(),content:nt(t)},!0)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;ie.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},ie.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},ie.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},ie.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const ae={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class ne extends ie{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");at(t,ae),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],n=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+n)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const oe={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class re extends ie{constructor(t){if(at(t,oe),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions.length&&t.namespace&&Object.keys(t.namespace).length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>I.ascending(t[s],e[s])||I.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,n){const o=a[a.length-1]||t;if(t[s]===o[s]&&t[i]<=o[e]){const s=Math.min(o[i],t[i]),n=Math.max(o[e],t[e]);t=Object.assign({},o,t,{[i]:s,[e]:n}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const le={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class he extends ie{constructor(t){t=at(t,le),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function n(t){const a=t[e.x_axis.field1],n=t[e.x_axis.field2],o=(a+n)/2,r=[[s(a),i(0)],[s(o),i(t[e.y_axis.field])],[s(n),i(0)]];return I.line().x((t=>t[0])).y((t=>t[1])).curve(I.curveNatural)(r)}const o=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(gt,e.style),o.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(o).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>n(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>n(t))),r.exit().remove(),o.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],n=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:n(t.data[s.y_axis.field]),y_max:n(0)}}}const ce={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:15,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class de extends ie{constructor(t){t=at(t,ce),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=I.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const n=I.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,n.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(n).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),n.exit().remove();const o=I.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));o.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(o).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),o.exit().remove();const r=I.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=I.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=I.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const ue={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5,tooltip:null};class _e extends ie{constructor(t){if((t=at(t,ue)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const n=t.x_scale,o=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?I.area().x((t=>+n(t[e]))).y0(+o(0)).y1((t=>+o(t[s]))):I.line().x((t=>+n(t[e]))).y((t=>+o(t[s]))).curve(I[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(gt,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;void 0===e&&(e=!0),this._global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this._global_statuses).forEach((t=>{this._global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const pe={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class ge extends ie{constructor(t){t=at(t,pe),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments)}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],n=I.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const n=+t[e](s.y);return isNaN(n)?a[i]:n}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",n).call(gt,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=I.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ye={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class fe extends ie{constructor(t){(t=at(t,ye)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),n=Math.sqrt(a/Math.PI);return{x_min:e-n,x_max:e+n,y_min:i-n,y_max:i+n}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,n=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,o=(t,a)=>{const n=+t.attr("x"),o=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",n-o),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",n+o),i&&a.attr("x2",r+l))};t._label_texts.each((function(e,a){const r=I.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>n){const e=i?I.select(t._label_lines.nodes()[a]):null;o(r,e)}})),t._label_texts.each((function(e,n){const r=I.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?I.select(t._label_lines.nodes()[n]):null;t._label_texts.each((function(){const t=I.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(o(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=o.attr("y"),c=.5*(r.topp?(g=d-+n,d=+n,u-=g):u+l.height/2>p&&(g=u-+h,u=+h,d-=g),a.attr("y",d),o.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t._label_texts.nodes();t._label_lines.attr("y2",((t,s)=>I.select(e[s]).attr("y")))}this._label_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let n=this._applyFilters();if(n.forEach((t=>{let n=e(t[this.layout.x_axis.field]),o=s(t[this.layout.y_axis.field]);isNaN(n)&&(n=-1e3),isNaN(o)&&(o=-1e3),t[i]=n,t[a]=o})),this.layout.coalesce.active&&n.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:o,x_gap:r,y_gap:l}=this.layout.coalesce;n=function(t,e,s,i,a,n,o){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function _(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function p(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],f=g>=e&&g<=s&&y>=a&&y<=n;t.lz_is_match||!f?(_(),r.push(t)):null===c?p(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=o?u.push(t):(_(),p(g,y,t))})),_(),r}(n,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(o)?s(+o):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=n.filter(t)}else e=n;this._label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const o=`lz-data_layer-${this.layout.type}-label`,r=this._label_groups.enter().append("g").attr("class",o);this._label_texts&&this._label_texts.remove(),this._label_texts=this._label_groups.merge(r).append("text").text((e=>Ht(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(gt,t.layout.label.style||{}),t.layout.label.lines&&(this._label_lines&&this._label_lines.remove(),this._label_lines=this._label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(gt,t.layout.label.lines.style||{})),this._label_groups.exit().remove()}else this._label_texts&&this._label_texts.remove(),this._label_lines&&this._label_lines.remove(),this._label_groups&&this._label_groups.remove();const o=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(n,(t=>t[this.layout.id_field])),r=I.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>ot(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;o.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(o).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),o.exit().remove(),this.layout.label&&(this.flip_labels(),this._label_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=I.select(I.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class me extends fe{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],n="string"==typeof i?i.toLowerCase():i,o="string"==typeof a?a.toLowerCase():a;return n===o?0:n{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],n=i[e],o=s[a]||[n,n];s[a]=[Math.min(o[0],n),Math.max(o[1],n)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const o=i[t];let r;switch(s){case"left":r=o[0];break;case"center":const t=o[1]-o[0];r=o[0]+(0!==t?t:o[0])/2;break;case"right":r=o[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const be=new c;for(let[t,e]of Object.entries(o))be.add(t,e);const xe=be,ve=7.301,we={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{assoc:variant|htmlescape}}
    \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
    \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
    '},$e=function(){const t=nt(we);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),ze={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

    {{gene_name|htmlescape}}

    Gene ID: {{gene_id|htmlescape}}
    Transcript ID: {{transcript_id|htmlescape}}
    {{#if pLI}}
    ConstraintExpected variantsObserved variantsConst. Metric
    Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
    o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
    Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
    o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
    pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
    o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

    {{/if}}More data on gnomAD'},ke={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{catalog:variant|htmlescape}}
    Catalog entries: {{n_catalog_matches|htmlescape}}
    Top Trait: {{catalog:trait|htmlescape}}
    Top P Value: {{catalog:log_pvalue|logtoscinotation}}
    More: GWAS catalog / dbSNP'},Ee={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
    {{access:start1|htmlescape}}-{{access:end1|htmlescape}}
    Promoter
    {{access:start2|htmlescape}}-{{access:end2|htmlescape}}
    {{#if access:target}}Target: {{access:target|htmlescape}}
    {{/if}}Score: {{access:score|htmlescape}}"},Me={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:ve},Se={id:"recombrate",namespace:{recomb:"recomb"},data_operations:[{type:"fetch",from:["recomb"]}],type:"line",tag:"recombination",z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"recomb:position"},y_axis:{axis:2,field:"recomb:recomb_rate",floor:0,ceiling:100}},Ne={namespace:{assoc:"assoc",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)"]},{type:"left_match",name:"assoc_plus_ld",requires:["assoc","ld"],params:["assoc:position","ld:position2"]}],id:"associationpvalues",type:"scatter",tag:"association",id_field:"assoc:variant",coalesce:{active:!0},point_shape:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"diamond"}},{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"assoc:beta",stderr_beta_field:"assoc:se"}},"circle"],point_size:{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:80,else:40}},color:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"#9632b8"}},{scale_function:"numerical_bin",field:"ld:correlation",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{label:"LD (r²)",label_size:14},{shape:"ribbon",orientation:"vertical",width:10,height:15,color_stops:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"],tick_labels:[0,.2,.4,.6,.8,1]}],label:null,z_index:2,x_axis:{field:"assoc:position"},y_axis:{axis:1,field:"assoc:log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(we)},Ae={id:"coaccessibility",type:"arcs",tag:"coaccessibility",namespace:{access:"access"},data_operations:[{type:"fetch",from:["access"]}],match:{send:"access:target",receive:"access:target"},id_field:"{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}",filters:[{field:"access:score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"access:start1",field2:"access:start2"},y_axis:{axis:1,field:"access:score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(Ee)},Oe=function(){let t=nt(Ne);return t=at({id:"associationpvaluescatalog",fill_opacity:.7},t),t.data_operations.push({type:"assoc_to_gwas_catalog",name:"assoc_catalog",requires:["assoc_plus_ld","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}),t.tooltip.html+='{{#if catalog:rsid}}
    See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t}(),Te={id:"phewaspvalues",type:"category_scatter",tag:"phewas",namespace:{phewas:"phewas"},data_operations:[{type:"fetch",from:["phewas"]}],point_shape:[{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"phewas:beta",stderr_beta_field:"phewas:se"}},"circle"],point_size:70,tooltip_positioning:"vertical",id_field:"{{phewas:trait_group}}_{{phewas:trait_label}}",x_axis:{field:"lz_auto_x",category_field:"phewas:trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"phewas:log_pvalue",floor:0,upper_buffer:.15},color:[{field:"phewas:trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Trait: {{phewas:trait_label|htmlescape}}
    \nTrait Category: {{phewas:trait_group|htmlescape}}
    \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
    β: {{phewas:beta|scinotation|htmlescape}}
    {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}"},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{phewas:trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"phewas:log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},Le={namespace:{gene:"gene",constraint:"constraint"},data_operations:[{type:"fetch",from:["gene","constraint(gene)"]},{name:"gene_constraint",type:"genes_to_gnomad_constraint",requires:["gene","constraint"]}],id:"genes",type:"genes",tag:"genes",id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ze)},je=at({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},nt(Le)),Pe={namespace:{assoc:"assoc",catalog:"catalog"},data_operations:[{type:"fetch",from:["assoc","catalog"]},{type:"assoc_to_gwas_catalog",name:"assoc_plus_ld",requires:["assoc","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}],id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:"#0000CC",filters:[{field:"catalog:rsid",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ke),tooltip_positioning:"top"},Re={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},Ie={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},Ce={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},De={widgets:[{type:"title",title:"LocusZoom",subtitle:`v${l}`,position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Be=function(){const t=nt(De);return t.widgets.push(nt(Re)),t}(),Ue=function(){const t=nt(De);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),qe={id:"association",tag:"association",min_height:200,height:300,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:50},y2:{label:"Recombination Rate (cM/Mb)",label_offset:46}},legend:{orientation:"vertical",origin:{x:75,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Me),nt(Se),nt(Ne)]},Fe={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:40,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Ae)]},He=function(){let t=nt(qe);return t=at({id:"associationcatalog"},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{catalog:trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"catalog:trait",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve},{field:"ld:correlation",operator:">",value:.4}],style:{"font-size":"12px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[nt(Me),nt(Se),nt(Oe)],t}(),Ge={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:55,bottom:20,left:70},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},nt(Ie)),t}(),data_layers:[nt(je)]},Je={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:55,bottom:120,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:50}},data_layers:[nt(Me),nt(Te)]},Ze={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:55,bottom:10,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Pe)]},Ke={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[nt(qe),nt(Ge)]},Ve={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[Ze,He,Ge]},We={width:800,responsive_resize:!0,toolbar:De,panels:[nt(Je),at({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"}}},nt(Ge))],mouse_guide:!1},Ye={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:nt(De),panels:[nt(Fe),function(){const t=Object.assign({height:270},nt(Ge)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},Xe={standard_association:we,standard_association_with_label:$e,standard_genes:ze,catalog_variant:ke,coaccessibility:Ee},Qe={ldlz2_pop_selector:Re,gene_selector_menu:Ie},ts={standard_panel:Ce,standard_plot:De,standard_association:Be,region_nav_plot:Ue},es={significance:Me,recomb_rate:Se,association_pvalues:Ne,coaccessibility:Ae,association_pvalues_catalog:Oe,phewas_pvalues:Te,genes:Le,genes_filtered:je,annotation_catalog:Pe},ss={association:qe,coaccessibility:Fe,association_catalog:He,genes:Ge,phewas:Je,annotation_catalog:Ze},is={standard_association:Ke,association_catalog:Ve,standard_phewas:We,coaccessibility:Ye};const as=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);const a=s.namespace;i.namespace||delete s.namespace;let n=at(s,i);return a&&(n=it(n,a)),nt(n)}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=nt(s);return"data_layer"===t&&a.namespace&&(a._auto_fields=[...rt(a,Object.keys(a.namespace))].sort()),super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return at(t,e)}renameField(){return lt(...arguments)}mutate_attrs(){return ht(...arguments)}query_attrs(){return ct(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))as.add(t,s,i);const ns=as,os=new h;function rs(t){return(e,s,...i)=>{if(2!==s.length)throw new Error("Join functions must receive exactly two recordsets");return t(...s,...i)}}os.add("left_match",rs(x)),os.add("inner_match",rs((function(t,e,s,i){return b("inner",...arguments)}))),os.add("full_outer_match",rs((function(t,e,s,i){return b("outer",...arguments)}))),os.add("assoc_to_gwas_catalog",rs((function(t,e,s,i,a){if(!t.length)return t;const n=m(e,i),o=[];for(let t of n.values()){let e,s=0;for(let i of t){const t=i[a];t>=s&&(e=i,s=t)}e.n_catalog_matches=t.length,o.push(e)}return x(t,o,s,i)}))),os.add("genes_to_gnomad_constraint",rs((function(t,e){return t.forEach((function(t){const s=`_${t.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=e[s]&&e[s].gnomad_constraint;i&&Object.keys(i).forEach((function(e){let s=i[e];void 0===t[e]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),t[e]=s)}))})),t})));const ls=os;const hs={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return I.select(t).html(""),I.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!I.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Ut(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Ft(s[2]),e=Ft(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Ft(s[2]),end:Ft(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Ft(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=I.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(gt,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||R}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:R,DataLayers:xe,DataFunctions:ls,Layouts:ns,MatchFunctions:Jt,ScaleFunctions:ee,TransformationFunctions:Z,Widgets:jt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),R}},cs=[];hs.use=function(t,...e){if(!cs.includes(t)){if(e.unshift(hs),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}cs.push(t)}};const ds=hs})(),LocusZoom=i.default})(); //# sourceMappingURL=locuszoom.app.min.js.map \ No newline at end of file diff --git a/dist/locuszoom.app.min.js.map b/dist/locuszoom.app.min.js.map index 0648c2f0..5e10135c 100644 --- a/dist/locuszoom.app.min.js.map +++ b/dist/locuszoom.app.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/./node_modules/@hapi/hoek/lib/assert.js","webpack://[name]/./node_modules/@hapi/hoek/lib/error.js","webpack://[name]/./node_modules/@hapi/hoek/lib/stringify.js","webpack://[name]/./node_modules/@hapi/topo/lib/index.js","webpack://[name]/./node_modules/just-clone/index.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./node_modules/undercomplicate/esm/lru_cache.js","webpack://[name]/./node_modules/undercomplicate/esm/util.js","webpack://[name]/./node_modules/undercomplicate/esm/requests.js","webpack://[name]/./node_modules/undercomplicate/esm/joins.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./node_modules/undercomplicate/esm/adapter.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/registry/data_ops.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["AssertError","module","exports","condition","args","length","Error","Stringify","super","filter","arg","map","message","join","captureStackTrace","this","assert","JSON","stringify","apply","err","Assert","internals","_items","nodes","options","before","concat","after","group","sort","includes","Array","isArray","node","item","seq","push","manual","valid","_sort","others","other","Object","assign","mergeSort","i","graph","graphAfters","create","groups","expandedGroups","graphNodeItem","ancestors","children","child","visited","sorted","next","j","shouldSeeCount","seenCount","k","seqIndex","value","sortedItem","a","b","getRegExpFlags","regExp","source","flags","global","ignoreCase","multiline","sticky","unicode","clone","obj","result","key","type","toString","call","slice","Date","getTime","RegExp","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","prop","prototype","hasOwnProperty","r","Symbol","toStringTag","RegistryBase","Map","name","has","override","set","delete","from","keys","ClassRegistry","parent_name","source_name","overrides","console","warn","arguments","base","sub","add","LLNode","metadata","prev","LRUCache","max_size","_max_size","_cur_size","_store","_head","_tail","cached","prior","_remove","old","callback","data","getLinkedData","shared_options","entities","dependencies","consolidate","parsed","spec","exec","name_alone","name_deps","deps","split","_parse_declaration","dag","toposort","entries","e","order","responses","provider","depends_on","this_result","Promise","all","then","prior_results","_provider_name","getData","values","all_results","groupBy","records","group_key","item_group","_any_match","left","right","left_key","right_key","right_index","results","left_match_value","right_matches","right_item","left_index","right_match_value","left_match","REGEX_MARKER","parseMarker","test","match","BaseApiAdapter","BaseLZAdapter","config","_config","cache_enabled","cache_size","_enable_cache","_cache","dependent_data","response_text","_buildRequestOptions","cache_key","_getCacheKey","resolve","_performRequest","text","_normalizeResponse","_cache_meta","catch","remove","_annotateRecords","_postProcessResponse","_url","url","_getURL","fetch","response","ok","statusText","parse","params","prefix_namespace","limit_fields","_prefix_namespace","_limit_fields","Set","chr","start","end","superset","find","md","row","reduce","acc","label","a_record","fieldname","suffixer","BaseUMAdapter","_genome_build","genome_build","build","constructor","N","every","fields","record","AssociationLZ","_source_id","request_options","GwasCatalogLZ","_validateBuildSource","source_query","GeneLZ","GeneConstraintLZ","state","genes_data","unique_gene_names","gene","gene_name","query","replace","body","method","headers","LDServer","assoc_data","assoc_variant_name","_findPrefixedKey","assoc_logp_name","refvar","best_hit","ldrefvar","best_logp","variant","log_pvalue","lz_is_ld_refvar","chrom","pos","ref","alt","coord","__find_ld_refvar","_skip_request","ld_refvar","ld_source","ld_population","ld_pop","population","encodeURIComponent","combined","chainRequests","payload","forEach","RecombLZ","StaticSource","_data","PheWASLZ","registry","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","transforms","full_name","field_name","transformations","val","transform","extra","undefined","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","m","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","paths","p","_","__","subject","uniqPaths","arr","elem","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","layout","shared_namespaces","requested_ns","merge","custom_layout","default_layout","property","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","findFields","prefixes","field_finder","all_ns","value_type","a_match","renameField","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","regex","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","DataOperation","join_type","initiator","_callable","_initiator","_params","plot_state","dependent_recordsets","data_layer","sources","_sources","namespace_options","data_operations","namespace_local_names","dependency_order","unshift","ns_pattern","local_name","global_name","dep_spec","requires","namecount","require_name","task","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","parentNode","insert","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","x","width","delay","setTimeout","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","position","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","index","indexOf","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","element","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","_panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","radioId","has_option","choice","defaultName","default_config_display_name","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","addWidget","widget","error","_panel_boundaries","dragging","_interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","line_height","_data_layer_ids_by_z_index","reverse","label_x","label_y","shape_factory","path_y","radius","PI","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","_initialized","_layout_idx","_state_id","_data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","_zoom_timeout","_event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","parseFloat","y_axis","axis","z_index","dlid","idx","layout_idx","data_layer_layout","target_layer","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","axes_config","base_x_range","range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","current_drag","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","scale","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","domain","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","namespace","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tickValues","tick_format","tickFormat","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","panel_boundaries","mouse_guide","Plot","datasource","_remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","_layer_state","_setDefaultState","target_panel","clearPanelData","opts","success_callback","from_layer","onerror","error_callback","base_prefix","layer_target","startsWith","is_valid_layer","some","listener","config_to_sources","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","mutateLayout","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","resize_listener","rescaleSVG","window","addEventListener","trackExternalListener","IntersectionObserver","threshold","observer","entry","intersectionRatio","observe","load_listener","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","_mouse_guide","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","ret","positionStringToInt","suffixre","mult","tokens","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","field_value","numerical_bin","breaks","null_value","curr","categorical_bin","categories","ordinal_cycle","stable_choice","max_cache_size","clear","hash","String","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","effect_direction","input","beta_field","stderr_beta_field","plus_result","neg_result","beta_val","se_val","id_field","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","_tooltips","_global_statuses","_data_contract","_entities","_dependencies","layer_order","current_index","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","fields_unseen","debug","lz_is_match","getDataLayer","getPanel","getPlot","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","tooltip_layout","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","_label_texts","da","dal","_label_lines","dax","abound","bbound","_label_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","_label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","version","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","custom_namespaces","_auto_fields","contents","list","_wrap_join","handle","catalog_data","assoc_key","catalog_key","catalog_logp_name","catalog_by_variant","catalog_flat","claims","best_variant","best","n_catalog_matches","constraint_data","alias","constraint","LocusZoom","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","source_id","Adapters","DataLayers","DataFunctions","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["'use strict';\n\nconst AssertError = require('./error');\n\nconst internals = {};\n\n\nmodule.exports = function (condition, ...args) {\n\n if (condition) {\n return;\n }\n\n if (args.length === 1 &&\n args[0] instanceof Error) {\n\n throw args[0];\n }\n\n throw new AssertError(args);\n};\n","'use strict';\n\nconst Stringify = require('./stringify');\n\n\nconst internals = {};\n\n\nmodule.exports = class extends Error {\n\n constructor(args) {\n\n const msgs = args\n .filter((arg) => arg !== '')\n .map((arg) => {\n\n return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);\n });\n\n super(msgs.join(' ') || 'Unknown error');\n\n if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$\n Error.captureStackTrace(this, exports.assert);\n }\n }\n};\n","'use strict';\n\nconst internals = {};\n\n\nmodule.exports = function (...args) {\n\n try {\n return JSON.stringify.apply(null, args);\n }\n catch (err) {\n return '[Cannot display object: ' + err.message + ']';\n }\n};\n","'use strict';\n\nconst Assert = require('@hapi/hoek/lib/assert');\n\n\nconst internals = {};\n\n\nexports.Sorter = class {\n\n constructor() {\n\n this._items = [];\n this.nodes = [];\n }\n\n add(nodes, options) {\n\n options = options || {};\n\n // Validate rules\n\n const before = [].concat(options.before || []);\n const after = [].concat(options.after || []);\n const group = options.group || '?';\n const sort = options.sort || 0; // Used for merging only\n\n Assert(!before.includes(group), `Item cannot come before itself: ${group}`);\n Assert(!before.includes('?'), 'Item cannot come before unassociated items');\n Assert(!after.includes(group), `Item cannot come after itself: ${group}`);\n Assert(!after.includes('?'), 'Item cannot come after unassociated items');\n\n if (!Array.isArray(nodes)) {\n nodes = [nodes];\n }\n\n for (const node of nodes) {\n const item = {\n seq: this._items.length,\n sort,\n before,\n after,\n group,\n node\n };\n\n this._items.push(item);\n }\n\n // Insert event\n\n if (!options.manual) {\n const valid = this._sort();\n Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');\n }\n\n return this.nodes;\n }\n\n merge(others) {\n\n if (!Array.isArray(others)) {\n others = [others];\n }\n\n for (const other of others) {\n if (other) {\n for (const item of other._items) {\n this._items.push(Object.assign({}, item)); // Shallow cloned\n }\n }\n }\n\n // Sort items\n\n this._items.sort(internals.mergeSort);\n for (let i = 0; i < this._items.length; ++i) {\n this._items[i].seq = i;\n }\n\n const valid = this._sort();\n Assert(valid, 'merge created a dependencies error');\n\n return this.nodes;\n }\n\n sort() {\n\n const valid = this._sort();\n Assert(valid, 'sort created a dependencies error');\n\n return this.nodes;\n }\n\n _sort() {\n\n // Construct graph\n\n const graph = {};\n const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives\n const groups = Object.create(null);\n\n for (const item of this._items) {\n const seq = item.seq; // Unique across all items\n const group = item.group;\n\n // Determine Groups\n\n groups[group] = groups[group] || [];\n groups[group].push(seq);\n\n // Build intermediary graph using 'before'\n\n graph[seq] = item.before;\n\n // Build second intermediary graph with 'after'\n\n for (const after of item.after) {\n graphAfters[after] = graphAfters[after] || [];\n graphAfters[after].push(seq);\n }\n }\n\n // Expand intermediary graph\n\n for (const node in graph) {\n const expandedGroups = [];\n\n for (const graphNodeItem in graph[node]) {\n const group = graph[node][graphNodeItem];\n groups[group] = groups[group] || [];\n expandedGroups.push(...groups[group]);\n }\n\n graph[node] = expandedGroups;\n }\n\n // Merge intermediary graph using graphAfters into final graph\n\n for (const group in graphAfters) {\n if (groups[group]) {\n for (const node of groups[group]) {\n graph[node].push(...graphAfters[group]);\n }\n }\n }\n\n // Compile ancestors\n\n const ancestors = {};\n for (const node in graph) {\n const children = graph[node];\n for (const child of children) {\n ancestors[child] = ancestors[child] || [];\n ancestors[child].push(node);\n }\n }\n\n // Topo sort\n\n const visited = {};\n const sorted = [];\n\n for (let i = 0; i < this._items.length; ++i) { // Looping through item.seq values out of order\n let next = i;\n\n if (ancestors[i]) {\n next = null;\n for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values\n if (visited[j] === true) {\n continue;\n }\n\n if (!ancestors[j]) {\n ancestors[j] = [];\n }\n\n const shouldSeeCount = ancestors[j].length;\n let seenCount = 0;\n for (let k = 0; k < shouldSeeCount; ++k) {\n if (visited[ancestors[j][k]]) {\n ++seenCount;\n }\n }\n\n if (seenCount === shouldSeeCount) {\n next = j;\n break;\n }\n }\n }\n\n if (next !== null) {\n visited[next] = true;\n sorted.push(next);\n }\n }\n\n if (sorted.length !== this._items.length) {\n return false;\n }\n\n const seqIndex = {};\n for (const item of this._items) {\n seqIndex[item.seq] = item;\n }\n\n this._items = [];\n this.nodes = [];\n\n for (const value of sorted) {\n const sortedItem = seqIndex[value];\n this.nodes.push(sortedItem.node);\n this._items.push(sortedItem);\n }\n\n return true;\n }\n};\n\n\ninternals.mergeSort = (a, b) => {\n\n return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);\n};\n","module.exports = clone;\n\n/*\n Deep clones all properties except functions\n\n var arr = [1, 2, 3];\n var subObj = {aa: 1};\n var obj = {a: 3, b: 5, c: arr, d: subObj};\n var objClone = clone(obj);\n arr.push(4);\n subObj.bb = 2;\n obj; // {a: 3, b: 5, c: [1, 2, 3, 4], d: {aa: 1}}\n objClone; // {a: 3, b: 5, c: [1, 2, 3], d: {aa: 1, bb: 2}}\n*/\n\nfunction clone(obj) {\n if (typeof obj == 'function') {\n return obj;\n }\n var result = Array.isArray(obj) ? [] : {};\n for (var key in obj) {\n // include prototype properties\n var value = obj[key];\n var type = {}.toString.call(value).slice(8, -1);\n if (type == 'Array' || type == 'Object') {\n result[key] = clone(value);\n } else if (type == 'Date') {\n result[key] = new Date(value.getTime());\n } else if (type == 'RegExp') {\n result[key] = RegExp(value.source, getRegExpFlags(value));\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction getRegExpFlags(regExp) {\n if (typeof regExp.source.flags == 'string') {\n return regExp.source.flags;\n } else {\n var flags = [];\n regExp.global && flags.push('g');\n regExp.ignoreCase && flags.push('i');\n regExp.multiline && flags.push('m');\n regExp.sticky && flags.push('y');\n regExp.unicode && flags.push('u');\n return flags.join('');\n }\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.14.0-beta.1';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","// Implement an LRU Cache\n\nclass LLNode {\n constructor(key, value, metadata = {}, prev = null, next = null) {\n this.key = key;\n this.value = value;\n this.metadata = metadata;\n this.prev = prev;\n this.next = next;\n }\n}\n\nclass LRUCache {\n constructor(max_size = 3) {\n this._max_size = max_size;\n this._cur_size = 0; // replace with map.size so we aren't managing manually?\n this._store = new Map();\n\n // Track LRU state\n this._head = null;\n this._tail = null;\n\n // Validate options\n if (max_size === null || max_size < 0) {\n throw new Error('Cache \"max_size\" must be >= 0');\n }\n }\n\n has(key) {\n // Check key membership without updating LRU\n return this._store.has(key);\n }\n\n get(key) {\n // Retrieve value from cache (if present) and update LRU cache accordingly\n const cached = this._store.get(key);\n if (!cached) {\n return null;\n }\n if (this._head !== cached) {\n // Rewrite the cached value to ensure it is head of the list\n this.add(key, cached.value);\n }\n return cached.value;\n }\n\n add(key, value, metadata = {}) {\n // Add an item. Forcibly replaces the existing cached value for the same key.\n if (this._max_size === 0) {\n // Don't cache items if cache has 0 size. Also prevent users from trying \"negative number for infinite cache\".\n return;\n }\n\n const prior = this._store.get(key);\n if (prior) {\n this._remove(prior);\n }\n\n const node = new LLNode(key, value, metadata, null, this._head);\n\n if (this._head) {\n this._head.prev = node;\n } else {\n this._tail = node;\n }\n\n this._head = node;\n this._store.set(key, node);\n\n if (this._max_size >= 0 && this._cur_size >= this._max_size) {\n const old = this._tail;\n this._tail = this._tail.prev;\n this._remove(old);\n }\n this._cur_size += 1;\n }\n\n\n // Cache manipulation methods\n clear() {\n this._head = null;\n this._tail = null;\n this._cur_size = 0;\n this._store = new Map();\n }\n\n // Public method, remove by key\n remove(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return false;\n }\n this._remove(cached);\n return true;\n }\n\n // Internal implementation, useful when working on list\n _remove(node) {\n if (node.prev !== null) {\n node.prev.next = node.next;\n } else {\n this._head = node.next;\n }\n\n if (node.next !== null) {\n node.next.prev = node.prev;\n } else {\n this._tail = node.prev;\n }\n this._store.delete(node.key);\n this._cur_size -= 1;\n }\n\n /**\n * Find a matching item in the cache, or return null. This is useful for \"approximate match\" semantics,\n * to check if something qualifies as a cache hit despite not having the exact same key.\n * (Example: zooming into a region, where the smaller region is a subset of something already cached)\n * @param callback\n * @returns {null|LLNode}\n */\n find(callback) {\n let node = this._head;\n while (node) {\n const next = node.next;\n if (callback(node)) {\n return node;\n }\n node = next;\n }\n }\n}\n\nexport { LRUCache };\n","import justclone from 'just-clone';\n\n/**\n * The \"just-clone\" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.\n * @param data\n * @returns {*}\n */\nfunction clone(data) {\n if (typeof data !== 'object') {\n return data;\n }\n return justclone(data);\n}\n\nexport { clone };\n","/**\n * Perform a series of requests, respecting order of operations\n */\n\nimport {Sorter} from '@hapi/topo';\n\n\nfunction _parse_declaration(spec) {\n // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.\n const parsed = /^(?\\w+)$|((?\\w+)+\\(\\s*(?[^)]+?)\\s*\\))/.exec(spec);\n if (!parsed) {\n throw new Error(`Unable to parse dependency specification: ${spec}`);\n }\n\n let {name_alone, name_deps, deps} = parsed.groups;\n if (name_alone) {\n return [name_alone, []];\n }\n\n deps = deps.split(/\\s*,\\s*/);\n return [name_deps, deps];\n}\n\nfunction getLinkedData(shared_options, entities, dependencies, consolidate = true) {\n if (!dependencies.length) {\n return [];\n }\n\n const parsed = dependencies.map((spec) => _parse_declaration(spec));\n const dag = new Map(parsed);\n\n // Define the order to perform requests in, based on a DAG\n const toposort = new Sorter();\n for (let [name, deps] of dag.entries()) {\n try {\n toposort.add(name, {after: deps, group: name});\n } catch (e) {\n throw new Error(`Invalid or possible circular dependency specification for: ${name}`);\n }\n }\n const order = toposort.nodes;\n\n // Verify that all requested entities exist by name!\n const responses = new Map();\n for (let name of order) {\n const provider = entities.get(name);\n if (!provider) {\n throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);\n }\n\n // Each promise should only be triggered when the things it depends on have been resolved\n const depends_on = dag.get(name) || [];\n const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));\n\n const this_result = prereq_promises.then((prior_results) => {\n // Each request will be told the name of the provider that requested it. This can be used during post-processing,\n // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)\n // This has a secondary effect: it ensures that any changes made to \"shared\" options in one adapter will\n // not leak out to others via a mutable shared object reference.\n const options = Object.assign({_provider_name: name}, shared_options);\n return provider.getData(options, ...prior_results);\n });\n responses.set(name, this_result);\n }\n return Promise.all([...responses.values()])\n .then((all_results) => {\n if (consolidate) {\n // Some usages- eg fetch + data join tasks- will only require the last response in the sequence\n // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)\n return all_results[all_results.length - 1];\n }\n return all_results;\n });\n}\n\n\nexport {getLinkedData};\n\n// For testing only\nexport {_parse_declaration};\n","/**\n * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.\n */\nimport { clone } from './util';\n\n\nfunction groupBy(records, group_key) {\n const result = new Map();\n for (let item of records) {\n const item_group = item[group_key];\n\n if (typeof item_group === 'undefined') {\n throw new Error(`All records must specify a value for the field \"${group_key}\"`);\n }\n if (typeof item_group === 'object') {\n // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)\n throw new Error('Attempted to group on a field with non-primitive values');\n }\n\n let group = result.get(item_group);\n if (!group) {\n group = [];\n result.set(item_group, group);\n }\n group.push(item);\n }\n return result;\n}\n\n\nfunction _any_match(type, left, right, left_key, right_key) {\n // Helper that consolidates logic for all three join types\n const right_index = groupBy(right, right_key);\n const results = [];\n for (let item of left) {\n const left_match_value = item[left_key];\n const right_matches = right_index.get(left_match_value) || [];\n if (right_matches.length) {\n // Record appears on both left and right; equiv to an inner join\n results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));\n } else if (type !== 'inner') {\n // Record appears on left but not right\n results.push(clone(item));\n }\n }\n\n if (type === 'outer') {\n // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side\n const left_index = groupBy(left, left_key);\n for (let item of right) {\n const right_match_value = item[right_key];\n const left_matches = left_index.get(right_match_value) || [];\n if (!left_matches.length) {\n results.push(clone(item));\n }\n }\n }\n return results;\n}\n\n/**\n * Equivalent to LEFT OUTER JOIN in SQL.\n * @param left\n * @param right\n * @param left_key\n * @param right_key\n * @returns {*[]}\n */\nfunction left_match(left, right, left_key, right_key) {\n return _any_match('left', ...arguments);\n}\n\nfunction inner_match(left, right, left_key, right_key) {\n return _any_match('inner', ...arguments);\n}\n\nfunction full_outer_match(left, right, left_key, right_key) {\n return _any_match('outer', ...arguments);\n}\n\nexport {left_match, inner_match, full_outer_match, groupBy};\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 1 }]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 2 }]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nimport {BaseUrlAdapter} from 'undercomplicate';\n\nimport {parseMarker} from '../helpers/parse';\n\n// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs.\n// Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal\n// methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the\n// private API methods exist in the base class.\n\n/**\n * Replaced with the BaseLZAdapter class.\n * @public\n * @deprecated\n */\nclass BaseAdapter {\n constructor() {\n throw new Error('The \"BaseAdapter\" and \"BaseApiAdapter\" classes have been replaced in LocusZoom 0.14. See migration guide for details.');\n }\n}\n\n/**\n * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @deprecated\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {}\n\n\n/**\n * @param {object} config\n * @param [config.cache_enabled=true]\n * @param [config.cache_size=3]\n * @param [config.url]\n * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name.\n * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant)\n * Typically, this is only disabled if the response payload is very unusual\n * @param {String[]} [limit_fields=null] If an API returns far more data than is needed, this can be used to simplify\n * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.\n */\nclass BaseLZAdapter extends BaseUrlAdapter {\n constructor(config = {}) {\n if (config.params) {\n // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places.\n console.warn('Deprecation warning: all options in \"config.params\" should now be specified as top level keys.');\n Object.assign(config, config.params || {});\n delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality!\n }\n super(config);\n\n // Prefix the namespace for this source to all fieldnames: id -> assoc.id\n // This is useful for almost all layers because the layout object says where to find every field, exactly.\n // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on\n // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear\n // in the response. (gene_name instead of genes.gene_name)\n const { prefix_namespace = true, limit_fields } = config;\n this._prefix_namespace = prefix_namespace;\n this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields\n }\n\n /**\n * Determine how a particular request will be identified in cache. Most LZ requests are region based,\n * so the default is a string concatenation of `chr_start_end`\n * @param options Receives plot.state plus any other request options defined by this source\n * @returns {string}\n * @private\n */\n _getCacheKey(options) {\n // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default\n let {chr, start, end} = options; // Current view: plot.state\n\n // Does a prior cache hit qualify as a superset of the ROI?\n const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end);\n if (superset) {\n ({ chr, start, end } = superset.metadata);\n }\n\n // The default cache key is region-based, and this method only returns the region-based part of the cache hit\n // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check\n options._cache_meta = { chr, start, end };\n return `${chr}_${start}_${end}`;\n }\n\n /**\n * Add the \"local namespace\" as a prefix for every field returned for this request. Eg if the association api\n * returns a field called variant, and the source is referred to as \"assoc\" within a particular data layer, then\n * the returned records will have a field called \"assoc:variant\"\n *\n * @param records\n * @param options\n * @returns {*}\n * @private\n */\n _postProcessResponse(records, options) {\n if (!this._prefix_namespace || !Array.isArray(records)) {\n return records;\n }\n\n // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for\n // assoc data might see \"variant\" as \"assoc.variant\"\n const { _limit_fields } = this;\n const { _provider_name } = options;\n\n return records.map((row) => {\n return Object.entries(row).reduce(\n (acc, [label, value]) => {\n // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload.\n if (!_limit_fields || _limit_fields.has(label)) {\n acc[`${_provider_name}:${label}`] = value;\n }\n return acc;\n },\n {}\n );\n });\n }\n\n /**\n * Convenience method, manually called in LZ sources that deal with dependent data.\n *\n * In the last step of fetching data, LZ adds a prefix to each field name.\n * This means that operations like \"build query based on prior data\" can't just ask for \"log_pvalue\" because\n * they are receiving \"assoc:log_pvalue\" or some such unknown prefix.\n *\n * This helper lets us use dependent data more easily. Not every adapter needs to use this method.\n *\n * @param {Object} a_record One record (often the first one in a set of records)\n * @param {String} fieldname The desired fieldname, eg \"log_pvalue\"\n */\n _findPrefixedKey(a_record, fieldname) {\n const suffixer = new RegExp(`:${fieldname}$`);\n const match = Object.keys(a_record).find((key) => suffixer.test(key));\n if (!match) {\n throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`);\n }\n return match;\n }\n}\n\n\n/**\n * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies\n * of one particular web server.\n */\nclass BaseUMAdapter extends BaseLZAdapter {\n /**\n * @param {Object} config\n * @param {String} [config.build] The genome build to be used by all requests for this adapter.\n */\n constructor(config = {}) {\n super(config);\n // The UM portaldev API accepts an (optional) parameter \"genome_build\"\n this._genome_build = config.genome_build || config.build;\n }\n\n _validateBuildSource(build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${this.constructor.name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`);\n }\n }\n\n // Special behavior for the UM portaldev API: col -> row format normalization\n /**\n * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.\n * @param response_text\n * @param options\n * @returns {Object[]}\n * @private\n */\n _normalizeResponse(response_text, options) {\n let data = super._normalizeResponse(...arguments);\n // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload\n data = data.data || data;\n\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the columns, and create an object for each row record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n}\n\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n *\n * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL\n */\nclass AssociationLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files\n const { source } = config;\n this._source_id = source;\n }\n\n _getURL (request_options) {\n const {chr, start, end} = request_options;\n const base = super._getURL(request_options);\n return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`;\n }\n}\n\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GwasCatalogLZ extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build] The genome build to use when requesting the specific genomic region.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n const source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given.\n // We will avoid transforming or modifying the payload.\n this._prefix_namespace = false;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GeneConstraintLZ extends BaseLZAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config = {}) {\n super(config);\n this._prefix_namespace = false;\n }\n\n _buildRequestOptions(state, genes_data) {\n const build = state.genome_build || this._config.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = new Set();\n for (let gene of genes_data) {\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n unique_gene_names.add(gene.gene_name);\n }\n\n state.query = [...unique_gene_names.values()].map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n state.build = build;\n return Object.assign({}, state);\n }\n\n _performRequest(options) {\n let {query, build} = options;\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears to set max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve([]);\n }\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n\n const url = this._getURL(options);\n\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.\n */\n _normalizeResponse(response_text) {\n if (typeof response_text !== 'string') {\n // If the query short-circuits, we receive an empty list instead of a string\n return response_text;\n }\n const data = JSON.parse(response_text);\n return data.data;\n }\n}\n\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant and yse that as the LD reference variant.\n *\n * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly\n * if this information is not provided.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass LDServer extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.method='rsquare'] The metric used to calculate LD\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n __find_ld_refvar(state, assoc_data) {\n const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant');\n const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue');\n\n // Determine the reference variant (via user selected OR automatic-per-track)\n let refvar;\n let best_hit = {};\n if (state.ldrefvar) {\n // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data\n refvar = state.ldrefvar;\n best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {};\n } else {\n // find highest log-value and associated var spec\n let best_logp = 0;\n for (let item of assoc_data) {\n const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item;\n if (log_pvalue > best_logp) {\n best_logp = log_pvalue;\n refvar = variant;\n best_hit = item;\n }\n }\n }\n\n // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting.\n // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase.\n best_hit.lz_is_ld_refvar = true;\n\n // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server,\n // the variant fields must be normalized to a specific format. All later LD operations will use that format.\n const match = parseMarker(refvar, true);\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n\n const [chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip?\n if (ref && alt) {\n refvar += `_${ref}/${alt}`;\n }\n\n const coord = +pos;\n // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably\n // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby.\n if ((coord && state.ldrefvar && state.chr) && (chrom !== state.chr || coord < state.start || coord > state.end)) {\n // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a\n // *copy* of plot.state, so wiping here doesn't remove the original value.\n state.ldrefvar = null;\n return this.__find_ld_refvar(state, assoc_data);\n }\n\n // Return the reference variant, in a normalized format suitable for LDServer queries\n return refvar;\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n // No variants, so no need to annotate association data with LD!\n // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region)\n base._skip_request = true;\n return base;\n }\n\n base.ld_refvar = this.__find_ld_refvar(state, assoc_data);\n\n // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config\n const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let ld_source = state.ld_source || this._config.source || '1000G';\n const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL\n\n if (ld_source === '1000G' && genome_build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n ld_source = '1000G-FRZ09';\n }\n\n this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option\n return Object.assign({}, base, { genome_build, ld_source, ld_population });\n }\n\n _getURL(request_options) {\n const method = this._config.method || 'rsquare';\n const {\n chr, start, end,\n ld_refvar,\n genome_build, ld_source, ld_population,\n } = request_options;\n\n const base = super._getURL(request_options);\n\n return [\n base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(ld_refvar),\n '&chrom=', encodeURIComponent(chr),\n '&start=', encodeURIComponent(start),\n '&stop=', encodeURIComponent(end),\n ].join('');\n }\n\n _getCacheKey(options) {\n // LD is keyed by more than just region; append other parameters to the base cache key\n const base = super._getCacheKey(options);\n const { ld_refvar, ld_source, ld_population } = options;\n return `${base}_${ld_refvar}_${ld_source}_${ld_population}`;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird.\n return Promise.resolve([]);\n }\n\n const url = this._getURL(options);\n\n // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseUMAdapter {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['position', 'recomb_rate'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {object} config.data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseLZAdapter {\n constructor(config = {}) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n super(...arguments);\n const { data } = config;\n if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key\n throw new Error(\"'StaticSource' must provide data as required option 'config.data'\");\n }\n this._data = data;\n }\n\n _performRequest(options) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will\n * be used to provide PheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const base = super._getURL(request_options);\n const url = [\n base,\n \"?filter=variant eq '\", encodeURIComponent(request_options.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n _getCacheKey(options) {\n // Not a region based source; don't do anything smart for cache check\n return this._getURL(options);\n }\n}\n\n// Deprecated symbols\nexport { BaseAdapter, BaseApiAdapter };\n\n// Usually used as a parent class for custom code\nexport { BaseLZAdapter, BaseUMAdapter };\n\n// Usually used as a standalone class\nexport {\n AssociationLZ,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","import {LRUCache} from './lru_cache';\nimport {clone} from './util';\n\nclass BaseAdapter {\n constructor(config = {}) {\n this._config = config;\n const {\n // Cache control\n cache_enabled = true,\n cache_size = 3,\n } = config;\n this._enable_cache = cache_enabled;\n this._cache = new LRUCache(cache_size);\n }\n\n _buildRequestOptions(options, dependent_data) {\n // Perform any pre-processing required that may influence the request. Receives an array with the payloads\n // for each request that preceded this one in the dependency chain\n // This method may optionally take dependent data into account\n return Object.assign({}, options);\n }\n\n _getCacheKey(options) {\n /* istanbul ignore next */\n if (this._enable_cache) {\n throw new Error('Method not implemented');\n }\n return null;\n }\n\n /**\n * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)\n * @param options\n * @returns {Promise}\n * @private\n */\n _performRequest(options) {\n /* istanbul ignore next */\n throw new Error('Not implemented');\n }\n\n _normalizeResponse(response_text, options) {\n // Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.\n return response_text;\n }\n\n /**\n * Perform custom client-side operations on the retrieved data. For example, add calculated fields or\n * perform rapid client-side filtering on cached data\n * @param records\n * @param {Object} options\n * @returns {*}\n * @private\n */\n _annotateRecords(records, options) {\n return records;\n }\n\n /**\n * A hook to transform the response after all operations are done. For example, this can be used to prefix fields\n * with a namespace unique to the request, like assoc.log_pvalue. (that way, annotations and validation can happen\n * on the actual API payload, without having to guess what the fields were renamed to).\n * @param records\n * @param options\n * @private\n */\n _postProcessResponse(records, options) {\n return records;\n }\n\n getData(options = {}, ...dependent_data) {\n // Public facing method to define, perform, and process the request\n options = this._buildRequestOptions(options, ...dependent_data);\n\n // Then retrieval and parse steps: parse + normalize response, annotate\n const cache_key = this._getCacheKey(options);\n\n let result;\n if (this._enable_cache && this._cache.has(cache_key)) {\n result = this._cache.get(cache_key);\n } else {\n // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)\n // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry\n // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,\n // even if the actual cache key wasn't an exact match\n result = Promise.resolve(this._performRequest(options))\n // Note: we cache the normalized (parsed) response\n .then((text) => this._normalizeResponse(text, options));\n this._cache.add(cache_key, result, options._cache_meta);\n // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request\n // Otherwise, temporary failures couldn't be resolved by trying again in a moment\n result.catch((e) => this._cache.remove(cache_key));\n }\n\n return result\n // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache\n .then((data) => clone(data))\n .then((records) => this._annotateRecords(records, options))\n .then((records) => this._postProcessResponse(records, options));\n }\n}\n\n\n/**\n * Fetch data over the web\n */\nclass BaseUrlAdapter extends BaseAdapter {\n constructor(config = {}) {\n super(config);\n this._url = config.url;\n }\n\n\n // Default cache key is the URL for the request.\n _getCacheKey(options) {\n return this._getURL(options);\n }\n\n _getURL(options) {\n return this._url;\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.\n // (not validating in constructor allows URL adapter to be used as more generic parent class)\n if (!this._url) {\n throw new Error('Web based resources must specify a resource URL as option \"url\"');\n }\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n // In most cases, we store the response as text so that the copy in cache is clean (no mutable references)\n return response.text();\n });\n }\n\n _normalizeResponse(response_text, options) {\n if (typeof response_text === 'string') {\n return JSON.parse(response_text);\n }\n // Some custom usages will return an object directly; return a copy of the object\n return response_text;\n }\n}\n\nexport { BaseAdapter, BaseUrlAdapter };\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import TRANSFORMS from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n // Two scenarios: we are requesting a field by full name, OR there are transforms to apply\n // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`\n const field_pattern = /^(?:\\w+:\\w+|^\\w+)(?:\\|\\w+)*$/;\n if (!field_pattern.test(field)) {\n throw new Error(`Invalid field specifier: '${field}'`);\n }\n\n const [name, ...transforms] = field.split('|');\n\n this.full_name = field; // fieldname + transforms\n this.field_name = name; // just fieldname\n this.transformations = transforms.map((name) => TRANSFORMS.get(name));\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (data[this.field_name] !== undefined) { // Fallback: value sans transforms\n val = data[this.field_name];\n } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations\n val = extra[this.field_name];\n } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {}\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * @module\n * @private\n */\nimport {getLinkedData} from 'undercomplicate';\n\nimport { DATA_OPS } from '../registry';\n\n\nclass DataOperation {\n /**\n * Perform a data operation (such as a join)\n * @param {String} join_type\n * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!\n * @param params Optional user/layout parameters to be passed to the data function\n */\n constructor(join_type, initiator, params) {\n this._callable = DATA_OPS.get(join_type);\n this._initiator = initiator;\n this._params = params || [];\n }\n\n getData(plot_state, ...dependent_recordsets) {\n // Most operations are joins: they receive two pieces of data (eg left + right)\n // Other ops are possible, like consolidating just one set of records to best value per key\n // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]\n\n // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options\n const context = {plot_state, data_layer: this._initiator};\n return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));\n }\n}\n\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a\n * designated order.\n *\n * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can\n * perform its own calculations, filter results, and apply transforms without influencing other layers.\n * (while still respecting a shared cache where appropriate)\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n /**\n * Parse the data layer configuration when a layer is first created.\n * Validate config, and return entities and dependencies in a format usable for data retrieval.\n * This is used by data layers, and also other data-retrieval functions (like subscribeToDate).\n *\n * Inherent assumptions:\n * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed.\n * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be\n * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the\n * removed adapter.\n * @param {Object} namespace_options\n * @param {Array} data_operations\n * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations,\n * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate\n * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care!\n * @returns {Array} Map of entities and list of dependencies\n */\n config_to_sources(namespace_options = {}, data_operations = [], initiator) {\n const entities = new Map();\n const namespace_local_names = Object.keys(namespace_options);\n\n // 1. Specify how to coordinate data. Precedence:\n // a) EXPLICIT fetch logic,\n // b) IMPLICIT auto-generate fetch order if there is only one NS,\n // c) Throw \"spec required\" error if > 1, because 2 adapters may need to be fetched in a sequence\n let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from}\n if (!dependency_order) {\n dependency_order = { type: 'fetch', from: namespace_local_names };\n data_operations.unshift(dependency_order);\n }\n\n // Validate that all NS items are available to the root requester in DataSources. All layers recognize a\n // default value, eg people copying the examples tend to have defined a datasource called \"assoc\"\n const ns_pattern = /^\\w+$/;\n for (let [local_name, global_name] of Object.entries(namespace_options)) {\n if (!ns_pattern.test(local_name)) {\n throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`);\n }\n\n const source = this._sources.get(global_name);\n if (!source) {\n throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`);\n }\n entities.set(local_name, source);\n\n // Note: Dependency spec checker will consider \"ld(assoc)\" to match a namespace called \"ld\"\n if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) {\n // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join.\n // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch\n // Thus the default behavior is \"fetch all namespaces as though they don't depend on anything.\n // If they depend on something, only then does \"data_ops[@type=fetch].from\" need to be mutated\n dependency_order.from.push(local_name);\n }\n }\n\n let dependencies = Array.from(dependency_order.from);\n\n // Now check all joins. Are namespaces valid? Are they requesting known data?\n for (let config of data_operations) {\n let {type, name, requires, params} = config;\n if (type !== 'fetch') {\n let namecount = 0;\n if (!name) {\n name = config.name = `join${namecount}`;\n namecount += 1;\n }\n\n if (entities.has(name)) {\n throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`);\n }\n requires.forEach((require_name) => {\n if (!entities.has(require_name)) {\n throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`);\n }\n });\n\n const task = new DataOperation(type, initiator, params);\n entities.set(name, task);\n dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB)\n }\n }\n return [entities, dependencies];\n }\n\n /**\n * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end)\n * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts.\n * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances\n * (things that implement a method getData).\n * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order\n * @returns {Promise}\n */\n getData(plot_state, entities, dependencies) {\n if (!dependencies.length) {\n return Promise.resolve([]);\n }\n // The last dependency (usually the last join operation) determines the last thing returned.\n return getLinkedData(plot_state, entities, dependencies, true);\n }\n}\n\n\nexport default Requester;\n\nexport {DataOperation as _JoinTask};\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import WIDGETS from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n const options = this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n this.addWidget(layout);\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown)\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Add a new widget to the toolbar.\n * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to:\n * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here.\n * - call widget.show() to ensure that the widget is initialized and rendered correctly\n * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default.\n * @param {Object} layout The layout object describing the desired widget\n * @returns {layout.type}\n */\n addWidget(layout) {\n try {\n const widget = WIDGETS.create(layout.type, layout, this);\n this.widgets.push(widget);\n return widget;\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 12,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n if (Array.isArray(this.parent.data_layers[id].layout.legend)) {\n this.parent.data_layers[id].layout.legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size || 12;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset]\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n if (typeof layout.id !== 'string' || !layout.id) {\n throw new Error('Panel layouts must specify \"id\"');\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this._layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this._state_id = this.id;\n this.state[this._state_id] = this.state[this._state_id] || {};\n } else {\n this.state = null;\n this._state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this._data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this._data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this._zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this._event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this._event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this._data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this._data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id]._layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n const target_layer = this.data_layers[id];\n if (!target_layer) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n target_layer.destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (target_layer.svg.container) {\n target_layer.svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(target_layer._layout_idx, 1);\n delete this.state[target_layer._state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id]._layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n const { cliparea } = this.layout;\n\n // Set and position the inner border, style if necessary\n const { margin } = this.layout;\n this.inner_border\n .attr('x', margin.left)\n .attr('y', margin.top)\n .attr('width', this.parent_plot.layout.width - (margin.left + margin.right))\n .attr('height', this.layout.height - (margin.top + margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n const axes_config = this.layout.axes;\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (axes_config.x.range) {\n base_x_range.start = axes_config.x.range.start || base_x_range.start;\n base_x_range.end = axes_config.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: cliparea.height, end: 0 };\n if (axes_config.y1.range) {\n base_y1_range.start = axes_config.y1.range.start || base_y1_range.start;\n base_y1_range.end = axes_config.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: cliparea.height, end: 0 };\n if (axes_config.y2.range) {\n base_y2_range.start = axes_config.y2.range.start || base_y2_range.start;\n base_y2_range.end = axes_config.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n let { _interaction } = this.parent;\n const current_drag = _interaction.dragging;\n if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (_interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = _interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = _interaction.zooming.center - margin.left - this.layout.origin.x;\n const offset_ratio = anchor / cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (current_drag) {\n switch (current_drag.method) {\n case 'background':\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n } else {\n anchor = current_drag.start_x - margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${current_drag.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y;\n ranges[y_shifted][1] = +current_drag.dragged_y;\n } else {\n anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3);\n ranges[y_shifted][0] = cliparea.height;\n ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent._interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n // Redefine b/c might have been changed during call to parent re-render\n _interaction = this.parent._interaction;\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this._zoom_timeout !== null) {\n clearTimeout(this._zoom_timeout);\n }\n this._zoom_timeout = setTimeout(() => {\n this.parent._interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this._initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((id) => {\n const axis = this.layout.axes[id];\n if (!Object.keys(axis).length || axis.render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n axis.render = false;\n } else {\n axis.render = true;\n axis.label = axis.label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n const layout = this.layout;\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n layout.height = Math.max(Math.round(+height), layout.min_height);\n }\n }\n layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0);\n layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', layout.height);\n }\n if (this._initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n const { cliparea, margin } = this.layout;\n if (!isNaN(top) && top >= 0) {\n margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (margin.top + margin.bottom > this.layout.height) {\n extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2);\n margin.top -= extra;\n margin.bottom -= extra;\n }\n if (margin.left + margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2);\n margin.left -= extra;\n margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n margin[m] = Math.max(margin[m], 0);\n });\n cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0);\n cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0);\n cliparea.origin.x = margin.left;\n cliparea.origin.y = margin.top;\n\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this._data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent._panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n const { parent } = this;\n const y_index = this.layout.y_index;\n if (parent._panel_ids_by_y_index[y_index - 1]) {\n parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1];\n parent._panel_ids_by_y_index[y_index - 1] = this.id;\n parent.applyPanelYIndexesToPanelLayouts();\n parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n const { _panel_ids_by_y_index } = this.parent;\n if (_panel_ids_by_y_index[this.layout.y_index + 1]) {\n _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1];\n _panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this._data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this._data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this._data_promises)\n .then(() => {\n this._initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([\\w+_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future.\n * @event data_from_layer\n * @property {object} data\n * @property {String} data.layer The fully qualified ID of the layer emitting this event\n * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum\n * element. It reflects all namespaces and data operations used by that layer.\n * @see event:any_lz_event\n */\n\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this._initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this._panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this._remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this._interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this._event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this._event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this._panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this._panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id]._layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer._layer_state;\n delete this.layout.state[layer._state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n const target_panel = this.panels[id];\n if (!target_panel) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this._panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n target_panel.loader.hide();\n target_panel.toolbar.destroy(true);\n target_panel.curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (target_panel.svg.container) {\n target_panel.svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(target_panel._layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id]._layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the\n * structure of the data, instead of just displaying something.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @listens event:data_from_layer\n * @param {Object} [opts] Options\n * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched.\n * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details.\n * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested,\n * this is usually required in order to specify dependency order and join operations. See data layer documentation for details.\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(opts, success_callback) {\n const { from_layer, namespace, data_operations, onerror } = opts;\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = onerror || function (err) {\n console.error('An error occurred while acting on an external callback', err);\n };\n\n if (from_layer) {\n // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places.\n const base_prefix = `${this.getBaseId()}.`;\n // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code.\n const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`;\n\n // Ensure that a valid layer exists to watch\n let is_valid_layer = false;\n for (let p of Object.values(this.panels)) {\n is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target);\n if (is_valid_layer) {\n break;\n }\n }\n if (!is_valid_layer) {\n throw new Error(`Could not subscribe to unknown data layer ${layer_target}`);\n }\n\n const listener = (eventData) => {\n if (eventData.data.layer !== layer_target) {\n // Same event name fires for many layers; only fire success cb for the one layer we want\n return;\n }\n try {\n success_callback(eventData.data.content, this);\n } catch (error) {\n error_callback(error);\n }\n };\n\n this.on('data_from_layer', listener);\n return listener;\n }\n\n // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of\n // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized\n // in connection to some other visualization)\n if (!namespace) {\n throw new Error(\"subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option\");\n }\n\n const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot.\n const listener = () => {\n try {\n // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely,\n // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame.\n this.lzd.getData(this.state, entities, dependencies)\n .then((new_data) => success_callback(new_data, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this._remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this._remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this._remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this._initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /**\n * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another,\n * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties,\n * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.\n * @public\n */\n mutateLayout() {\n Object.values(this.panels).forEach((panel) => {\n Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout());\n });\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n const { _interaction } = this;\n if (panel_id) {\n return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(_interaction.dragging || _interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this._panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this._initialized) {\n this._panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let panel of Object.values(this.panels)) {\n if (panel.layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_layout = panel.layout;\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel_layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel_layout.margin.right, 0);\n panel_layout.width += delta;\n panel_layout.margin.left = x_linked_margins.left;\n panel_layout.margin.right = x_linked_margins.right;\n panel_layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n const resize_listener = () => this.rescaleSVG();\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener\n // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed\n if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers\n const options = { root: document.documentElement, threshold: 0.9 };\n const observer = new IntersectionObserver((entries, observer) => {\n if (entries.some((entry) => entry.intersectionRatio > 0)) {\n this.rescaleSVG();\n }\n }, options);\n // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup\n observer.observe(this.container);\n }\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this._mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this._panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent._panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent._panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this._panel_boundaries.hide_timeout);\n this._panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this._panel_boundaries.hide_timeout = setTimeout(() => {\n this._panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this._mouse_guide.vertical.attr('x', -1);\n this._mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this._mouse_guide.vertical.attr('x', coords[0]);\n this._mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n const { _interaction } = this;\n if (_interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x;\n _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y;\n this.panels[_interaction.panel_id].render();\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this._initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this._interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n const { _interaction } = this;\n if (!_interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[_interaction.panel_id] != 'object') {\n this._interaction = {};\n return this;\n }\n const panel = this.panels[_interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel._data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (_interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (_interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (_interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(_interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this._interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} value value\n */\nconst if_value = (parameters, value) => {\n if (typeof value == 'undefined' || parameters.field_value !== value) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} value value\n * @returns {*}\n */\nconst numerical_bin = (parameters, value) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+value < prev || (+value >= prev && +value < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @function ordinal_cycle\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n *\n * @function stable_choice\n *\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, value) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return nullval;\n }\n if (+value <= parameters.breaks[0]) {\n return values[0];\n } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +value && breaks[idx] >= +value) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\n/**\n * Calculate the effect direction based on beta, or the combination of beta and standard error.\n * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.\n *\n * @function effect_direction\n * @param parameters\n * @param parameters.'+' The value to return if the effect direction is positive\n * @param parameters.'-' The value to return if the effect direction is positive\n * @param parameters.beta_field The name of the field containing beta\n * @param parameters.stderr_beta_field The name of the field containing stderr_beta\n * @param {Object} input This function should receive the entire datum object, rather than one single field\n * @returns {null}\n */\nfunction effect_direction(parameters, input) {\n if (input === undefined) {\n return null;\n }\n\n const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters;\n\n if (!beta_field || !stderr_beta_field) {\n throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`);\n }\n\n const beta_val = input[beta_field];\n const se_val = input[stderr_beta_field];\n\n if (beta_val !== undefined) {\n if (se_val !== undefined) {\n if ((beta_val - 2 * se_val) > 0) {\n return plus_result;\n } else if ((beta_val + 2 * se_val) < 0) {\n return neg_result || null;\n }\n } else {\n if (beta_val > 0) {\n return plus_result;\n } else if (beta_val < 0) {\n return neg_result;\n }\n }\n }\n // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid\n // about expected data formats for layouts.\n return null;\n}\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, findFields, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n/**\n * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.\n * @property {module:LocusZoom_DataFunctions|'fetch'} type\n * @property {String[]} [from] For operations of type \"fetch\", this is required. By default, it will fill in any items provided in \"namespace\" (everything specified in namespace triggers an adapter/network request)\n * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace).\n * Eg, for ld to be fetched after association data, specify \"ld(assoc)\". Most LocusZoom examples fill in all items, in order to make the examples more clear.\n * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation.\n * Eg, if the retrieved data is combined via several left joins in series: `name: \"assoc_plus_ld\"` -> feeds into `{name: \"final\", requires: [\"assoc_plus_ld\"]}`\n * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just\n * the names of other namespaces (or data operations) whose results must be available before a join can be performed\n * @property {String[]} params Any user-defined parameters that should be passed to the particular join function\n * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']`\n * Separate from this section, data functions will also receive a copy of \"plot.state\" automatically.\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or line for a path.\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\".\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n namespace: {},\n data_operations: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is\n * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely\n * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is\n * your job to assure that all of the expected fields are present in every element)\n * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data (\"assoc\")\n * to the globally unique name for something defined in LocusZoom.DataSources.\n * Namespaces allow a single layout to be reused to plot many tracks of the same type: \"given some form of association data, plot it\".\n * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse\n * a layout with a new provider of data- like plotting two association studies stacked together-\n * only the namespace section of the layout needs to be overridden.\n * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})`\n * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions})\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this._layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this._state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this._layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this._tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this._global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n\n // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval\n this._data_contract = new Set(); // List of all fields requested by the layout\n this._entities = new Map();\n this._dependencies = [];\n this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout\n }\n\n /****** Public interface: methods for manipulating the layer from other parts of LZ */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index + 1]) {\n layer_order[current_index] = layer_order[current_index + 1];\n layer_order[current_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index - 1]) {\n layer_order[current_index] = layer_order[current_index - 1];\n layer_order[current_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this._layer_state.extra_fields[id]) {\n this._layer_state.extra_fields[id] = {};\n }\n this._layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /**\n * A list of operations that should be run when the layout is mutated\n * Typically, these are things done once when a layout is first specified, that would not automatically\n * update when the layout was changed.\n * @public\n */\n mutateLayout() {\n // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract.\n if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester\n const { namespace, data_operations } = this.layout;\n this._data_contract = findFields(this.layout, Object.keys(namespace));\n const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this);\n this._entities = entities;\n this._dependencies = dependencies;\n }\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n *\n * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that\n * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data),\n * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}`\n * @protected\n * @param {Object} element The data associated with a particular element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression\n const id_field = this.layout.id_field;\n let value = element[id_field];\n if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) {\n // No field value was found directly, but if it looks like a template expression, next, try parsing that\n // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot!\n value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient\n }\n if (value === null || value === undefined) {\n // Neither exact field nor template options produced an ID\n throw new Error('Unable to generate element ID');\n }\n const element_id = value.toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Abstract method. It should be overridden by data layers that implement separate status\n * nodes, such as genes or intervals.\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or\n * a group of unrelated intervals (all markings grouped within a category).\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n\n // Does the data from the API satisfy the list of fields expected by this layout?\n // Not every record will have every possible field (example: left joins like assoc + ld). The check is \"did\n // we see this field at least once in any record at all\".\n if (this.data.length && this._data_contract.size) {\n const fields_unseen = new Set(this._data_contract);\n for (let record of this.data) {\n Object.keys(record).forEach((field) => fields_unseen.delete(field));\n if (!fields_unseen.size) {\n // Once every requested field has been seen in at least one record, no need to look at more records\n break;\n }\n }\n if (fields_unseen.size) {\n // Current implementation is a soft warning, so that certain \"incremental enhancement\" features\n // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info.\n // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data.\n console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in \"data_operations\" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`);\n }\n }\n\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value);\n }\n\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this._layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const _layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = _layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this._state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this._state_id] = _layer_state;\n }\n this._layer_state = _layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this._tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this._tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this._layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this._tooltips[id].selector.html('');\n this._tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this._tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this._tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this._tooltips[id]) {\n if (typeof this._tooltips[id].selector == 'object') {\n this._tooltips[id].selector.remove();\n }\n delete this._tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this._layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this._tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this._tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this._tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this._tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n const tooltip_layout = this.layout.tooltip;\n if (typeof tooltip_layout != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof tooltip_layout.show == 'string') {\n show_directive = { and: [ tooltip_layout.show ] };\n } else if (typeof tooltip_layout.show == 'object') {\n show_directive = tooltip_layout.show;\n }\n\n let hide_directive = {};\n if (typeof tooltip_layout.hide == 'string') {\n hide_directive = { and: [ tooltip_layout.hide ] };\n } else if (typeof tooltip_layout.hide == 'object') {\n hide_directive = tooltip_layout.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const _layer_state = this._layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (_layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this._layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this._layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this._layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this._layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self._state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies)\n .then((new_data) => {\n this.data = new_data;\n this.applyDataMethods();\n this._initialized = true;\n // Allow listeners (like subscribeToData) to see the information associated with a layer\n this.parent.emit(\n 'data_from_layer',\n { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin\n true\n );\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 12,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n tooltip: null,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this._global_statuses).forEach((global_status) => {\n if (this._global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n data_layer._label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this._label_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer._label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer._label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer._label_texts.nodes();\n data_layer._label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this._label_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this._label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this._label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this._label_texts) {\n this._label_texts.remove();\n }\n\n this._label_texts = this._label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this._label_lines) {\n this._label_lines.remove();\n }\n this._label_lines = this._label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this._label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this._label_texts) {\n this._label_texts.remove();\n }\n if (this._label_lines) {\n this._label_lines.remove();\n }\n if (this._label_groups) {\n this._label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this._label_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{assoc:variant|htmlescape}}
    \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
    \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
    `,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

    {{gene_name|htmlescape}}

    '\n + 'Gene ID: {{gene_id|htmlescape}}
    '\n + 'Transcript ID: {{transcript_id|htmlescape}}
    '\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
    ConstraintExpected variantsObserved variantsConst. Metric
    Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
    o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
    Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
    o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
    pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
    o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

    {{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{catalog:variant|htmlescape}}
    '\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
    '\n + 'Top Trait: {{catalog:trait|htmlescape}}
    '\n + 'Top P Value: {{catalog:log_pvalue|logtoscinotation}}
    '\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
    ' +\n '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
    ' +\n 'Promoter
    ' +\n '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
    ' +\n '{{#if access:target}}Target: {{access:target|htmlescape}}
    {{/if}}' +\n 'Score: {{access:score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n id: 'recombrate',\n namespace: { 'recomb': 'recomb' },\n data_operations: [\n { type: 'fetch', from: ['recomb'] },\n ],\n type: 'line',\n tag: 'recombination',\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: 'recomb:position',\n },\n y_axis: {\n axis: 2,\n field: 'recomb:recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)'],\n },\n {\n type: 'left_match',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'ld'],\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n id_field: 'assoc:variant',\n coalesce: {\n active: true,\n },\n point_shape: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 'diamond',\n },\n },\n {\n // Not every dataset will provide these params\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'assoc:beta',\n stderr_beta_field: 'assoc:se',\n },\n },\n 'circle',\n ],\n point_size: {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: 'ld:correlation',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { shape: 'diamond', color: '#9632b8', size: 40, label: 'LD Ref Var', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(219, 61, 17)', size: 40, label: '1.0 > r² ≥ 0.8', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(248, 195, 42)', size: 40, label: '0.8 > r² ≥ 0.6', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(110, 254, 104)', size: 40, label: '0.6 > r² ≥ 0.4', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(38, 188, 225)', size: 40, label: '0.4 > r² ≥ 0.2', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: 'rgb(70, 54, 153)', size: 40, label: '0.2 > r² ≥ 0.0', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#AAAAAA', size: 40, label: 'no r² data', class: 'lz-data_layer-scatter' },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: 'assoc:position',\n },\n y_axis: {\n axis: 1,\n field: 'assoc:log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n namespace: { 'access': 'access' },\n data_operations: [\n { type: 'fetch', from: ['access'] },\n ],\n match: { send: 'access:target', receive: 'access:target' },\n // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.\n id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',\n filters: [\n { field: 'access:score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: 'access:start1',\n field2: 'access:start2',\n },\n y_axis: {\n axis: 1,\n field: 'access:score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n\n base.data_operations.push({\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_catalog',\n requires: ['assoc_plus_ld', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n });\n\n base.tooltip.html += '{{#if catalog:rsid}}
    See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n return base;\n}();\n\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n namespace: { 'phewas': 'phewas' },\n data_operations: [\n { type: 'fetch', from: ['phewas'] },\n ],\n point_shape: [\n {\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'phewas:beta',\n stderr_beta_field: 'phewas:se',\n },\n },\n 'circle',\n ],\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',\n x_axis: {\n field: 'lz_auto_x', // Automatically added by the category_scatter layer\n category_field: 'phewas:trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: 'phewas:log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: 'phewas:trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Trait: {{phewas:trait_label|htmlescape}}
    \nTrait Category: {{phewas:trait_group|htmlescape}}
    \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
    β: {{phewas:beta|scinotation|htmlescape}}
    {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}`,\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{phewas:trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: 'phewas:log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n data_operations: [\n {\n type: 'fetch',\n from: ['gene', 'constraint(gene)'],\n },\n {\n name: 'gene_constraint',\n type: 'genes_to_gnomad_constraint',\n requires: ['gene', 'constraint'],\n },\n ],\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n data_operations: [\n {\n type: 'fetch', from: ['assoc', 'catalog'],\n },\n {\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n },\n ],\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: '#0000CC',\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'catalog:rsid', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n }\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 225,\n margin: { top: 35, right: 50, bottom: 40, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 28,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 40,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 55, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 50, bottom: 40, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 28,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{catalog:trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: 'catalog:trait', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: 'ld:correlation', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '10px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 50, bottom: 20, left: 50 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu)\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 50, bottom: 120, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 28,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 50, bottom: 10, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 32,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel)\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or applying namespaces.\n let base = super.get(type).get(name);\n\n // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.\n // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)\n const custom_namespaces = overrides.namespace;\n if (!base.namespace) {\n // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout\n // NOTE: The \"merge namespace\" behavior means that data layers can add new data easily, but this method\n // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).\n delete overrides.namespace;\n }\n let result = merge(overrides, base);\n\n if (custom_namespaces) {\n result = applyNamespaces(result, custom_namespaces);\n }\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n\n // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested\n // from external sources. This is purely a hint, because not every layout is generated through the registry.\n if (type === 'data_layer' && copy.namespace) {\n copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();\n }\n\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","/**\n * \"Data operation\" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results\n *\n * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation\n * is a \"join\", such as combining association + LD together into a single set of records for plotting. Several join\n * functions (that operate by analogy to SQL) are provided built-in.\n *\n * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):\n * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.\n * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,\n * this is the recommended path to do so)\n * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),\n * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,\n * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks\n * (PheWAS), automatic panel legends or color schemes (BED tracks), etc.\n *\n * Usually, a data operation receives two recordsets (the left and right members of the join, like \"assoc\" and \"ld\").\n * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network\n * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is\n * uncommon. (if possible, try to provide your data with fewer adapters/network requests!)\n *\n * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,\n * particularly for advanced features, may carry assumptions about field names/ formatting.\n * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,\n * or it may match two datasets based on a specific way of identifying the variant)\n *\n * @module LocusZoom_DataFunctions\n */\nimport {joins} from 'undercomplicate';\n\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"data join\" functions.\n * @alias module:LocusZoom~DataFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\nfunction _wrap_join(handle) {\n // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).\n\n // Many of our join functions are implemented with a different number of arguments than what a datafunction\n // actually receives. (eg, a join function is generic and doesn't care about \"context\" information like plot.state)\n // This wrapper is simple shared code to handle required validation and conversion stuff.\n return (context, deps, ...params) => {\n if (deps.length !== 2) {\n throw new Error('Join functions must receive exactly two recordsets');\n }\n return handle(...deps, ...params);\n };\n}\n\n// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to\n// pick the most significant claim in the catalog for a variant, rather than joining every possible match.\n// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.\nfunction assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {\n if (!assoc_data.length) {\n return assoc_data;\n }\n\n // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each\n const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);\n\n const catalog_flat = []; // Store only the top significant claim for each catalog variant entry\n for (let claims of catalog_by_variant.values()) {\n // Find max item within this set of claims, push that to catalog_\n let best = 0;\n let best_variant;\n for (let item of claims) {\n const val = item[catalog_logp_name];\n if ( val >= best) {\n best_variant = item;\n best = val;\n }\n }\n best_variant.n_catalog_matches = claims.length;\n catalog_flat.push(best_variant);\n }\n return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);\n}\n\n// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.\nfunction genes_to_gnomad_constraint(genes_data, constraint_data) {\n genes_data.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return genes_data;\n}\n\n\n/**\n * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset\n *\n * @function\n * @name left_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('left_match', _wrap_join(joins.left_match));\n\n/**\n * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.\n *\n * @function\n * @name inner_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('inner_match', _wrap_join(joins.inner_match));\n\n/**\n * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.\n *\n * @function\n * @name full_outer_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('full_outer_match', _wrap_join(joins.full_outer_match));\n\n/**\n * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.\n *\n * @function\n * @name assoc_to_gwas_catalog\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: assoc records, then catalog records\n * @param {String} assoc_key The name of the key field in association data, eg variant ID\n * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID\n * @param {String} catalog_log_p_name The name of the \"log_pvalue\" field in gwas catalog data, used to choose the most significant claim for a given variant\n */\nregistry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));\n\n/**\n * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).\n *\n * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.\n *\n * @function\n * @name genes_to_gnomad_constraint\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data\n */\nregistry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));\n\nexport default registry;\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n DATA_OPS as DataFunctions,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n WIDGETS as Widgets,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n DataFunctions,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/./node_modules/@hapi/hoek/lib/assert.js","webpack://[name]/./node_modules/@hapi/hoek/lib/error.js","webpack://[name]/./node_modules/@hapi/hoek/lib/stringify.js","webpack://[name]/./node_modules/@hapi/topo/lib/index.js","webpack://[name]/./node_modules/just-clone/index.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./node_modules/undercomplicate/esm/lru_cache.js","webpack://[name]/./node_modules/undercomplicate/esm/util.js","webpack://[name]/./node_modules/undercomplicate/esm/requests.js","webpack://[name]/./node_modules/undercomplicate/esm/joins.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./node_modules/undercomplicate/esm/adapter.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/registry/data_ops.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["AssertError","module","exports","condition","args","length","Error","Stringify","super","filter","arg","map","message","join","captureStackTrace","this","assert","JSON","stringify","apply","err","Assert","internals","_items","nodes","options","before","concat","after","group","sort","includes","Array","isArray","node","item","seq","push","manual","valid","_sort","others","other","Object","assign","mergeSort","i","graph","graphAfters","create","groups","expandedGroups","graphNodeItem","ancestors","children","child","visited","sorted","next","j","shouldSeeCount","seenCount","k","seqIndex","value","sortedItem","a","b","getRegExpFlags","regExp","source","flags","global","ignoreCase","multiline","sticky","unicode","clone","obj","result","key","type","toString","call","slice","Date","getTime","RegExp","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","prop","prototype","hasOwnProperty","r","Symbol","toStringTag","RegistryBase","Map","name","has","override","set","delete","from","keys","ClassRegistry","parent_name","source_name","overrides","console","warn","arguments","base","sub","add","LLNode","metadata","prev","LRUCache","max_size","_max_size","_cur_size","_store","_head","_tail","cached","prior","_remove","old","callback","data","getLinkedData","shared_options","entities","dependencies","consolidate","parsed","spec","exec","name_alone","name_deps","deps","split","_parse_declaration","dag","toposort","entries","e","order","responses","provider","depends_on","this_result","Promise","all","then","prior_results","_provider_name","getData","values","all_results","groupBy","records","group_key","item_group","_any_match","left","right","left_key","right_key","right_index","results","left_match_value","right_matches","right_item","left_index","right_match_value","left_match","REGEX_MARKER","parseMarker","test","match","BaseApiAdapter","BaseLZAdapter","config","_config","cache_enabled","cache_size","_enable_cache","_cache","dependent_data","response_text","_buildRequestOptions","cache_key","_getCacheKey","resolve","_performRequest","text","_normalizeResponse","_cache_meta","catch","remove","_annotateRecords","_postProcessResponse","_url","url","_getURL","fetch","response","ok","statusText","parse","params","prefix_namespace","limit_fields","_prefix_namespace","_limit_fields","Set","chr","start","end","superset","find","md","row","reduce","acc","label","a_record","fieldname","suffixer","BaseUMAdapter","_genome_build","genome_build","build","constructor","N","every","fields","record","AssociationLZ","_source_id","request_options","GwasCatalogLZ","_validateBuildSource","source_query","GeneLZ","GeneConstraintLZ","state","genes_data","unique_gene_names","gene","gene_name","query","replace","body","method","headers","LDServer","assoc_data","assoc_variant_name","_findPrefixedKey","assoc_logp_name","refvar","best_hit","ldrefvar","best_logp","variant","log_pvalue","lz_is_ld_refvar","chrom","pos","ref","alt","coord","__find_ld_refvar","_skip_request","ld_refvar","ld_source","ld_population","ld_pop","population","encodeURIComponent","combined","chainRequests","payload","forEach","RecombLZ","StaticSource","_data","PheWASLZ","registry","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","transforms","full_name","field_name","transformations","val","transform","extra","undefined","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","m","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","paths","p","_","__","subject","uniqPaths","arr","elem","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","layout","shared_namespaces","requested_ns","merge","custom_layout","default_layout","property","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","findFields","prefixes","field_finder","all_ns","value_type","a_match","renameField","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","regex","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","DataOperation","join_type","initiator","_callable","_initiator","_params","plot_state","dependent_recordsets","data_layer","sources","_sources","namespace_options","data_operations","namespace_local_names","dependency_order","unshift","ns_pattern","local_name","global_name","dep_spec","requires","namecount","require_name","task","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","parentNode","insert","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","x","width","delay","setTimeout","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","position","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","index","indexOf","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","element","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","_panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","radioId","has_option","choice","defaultName","default_config_display_name","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","addWidget","widget","error","_panel_boundaries","dragging","_interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","line_height","_data_layer_ids_by_z_index","reverse","layer_legend","label_x","label_y","shape_factory","path_y","is_horizontal","color_stops","all_elements","ribbon_group","axis_group","axis_offset","tick_labels","range","scale","domain","axis","tickSize","tickValues","tickFormat","v","to_next_marking","radius","PI","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","_initialized","_layout_idx","_state_id","_data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","_zoom_timeout","_event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","parseFloat","y_axis","z_index","dlid","idx","layout_idx","data_layer_layout","target_layer","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","axes_config","base_x_range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","current_drag","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","namespace","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tick_format","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","panel_boundaries","mouse_guide","Plot","datasource","_remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","_layer_state","_setDefaultState","target_panel","clearPanelData","opts","success_callback","from_layer","onerror","error_callback","base_prefix","layer_target","startsWith","is_valid_layer","some","listener","config_to_sources","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","mutateLayout","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","resize_listener","rescaleSVG","window","addEventListener","trackExternalListener","IntersectionObserver","threshold","observer","entry","intersectionRatio","observe","load_listener","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","_mouse_guide","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","ret","positionStringToInt","suffixre","mult","tokens","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","field_value","numerical_bin","breaks","null_value","curr","categorical_bin","categories","ordinal_cycle","stable_choice","max_cache_size","clear","hash","String","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","effect_direction","input","beta_field","stderr_beta_field","plus_result","neg_result","beta_val","se_val","id_field","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","_tooltips","_global_statuses","_data_contract","_entities","_dependencies","layer_order","current_index","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","fields_unseen","debug","lz_is_match","getDataLayer","getPanel","getPlot","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","tooltip_layout","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","_label_texts","da","dal","_label_lines","dax","abound","bbound","_label_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","_label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","version","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","custom_namespaces","_auto_fields","contents","list","_wrap_join","handle","catalog_data","assoc_key","catalog_key","catalog_logp_name","catalog_by_variant","catalog_flat","claims","best_variant","best","n_catalog_matches","constraint_data","alias","constraint","LocusZoom","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","source_id","Adapters","DataLayers","DataFunctions","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["'use strict';\n\nconst AssertError = require('./error');\n\nconst internals = {};\n\n\nmodule.exports = function (condition, ...args) {\n\n if (condition) {\n return;\n }\n\n if (args.length === 1 &&\n args[0] instanceof Error) {\n\n throw args[0];\n }\n\n throw new AssertError(args);\n};\n","'use strict';\n\nconst Stringify = require('./stringify');\n\n\nconst internals = {};\n\n\nmodule.exports = class extends Error {\n\n constructor(args) {\n\n const msgs = args\n .filter((arg) => arg !== '')\n .map((arg) => {\n\n return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);\n });\n\n super(msgs.join(' ') || 'Unknown error');\n\n if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$\n Error.captureStackTrace(this, exports.assert);\n }\n }\n};\n","'use strict';\n\nconst internals = {};\n\n\nmodule.exports = function (...args) {\n\n try {\n return JSON.stringify.apply(null, args);\n }\n catch (err) {\n return '[Cannot display object: ' + err.message + ']';\n }\n};\n","'use strict';\n\nconst Assert = require('@hapi/hoek/lib/assert');\n\n\nconst internals = {};\n\n\nexports.Sorter = class {\n\n constructor() {\n\n this._items = [];\n this.nodes = [];\n }\n\n add(nodes, options) {\n\n options = options || {};\n\n // Validate rules\n\n const before = [].concat(options.before || []);\n const after = [].concat(options.after || []);\n const group = options.group || '?';\n const sort = options.sort || 0; // Used for merging only\n\n Assert(!before.includes(group), `Item cannot come before itself: ${group}`);\n Assert(!before.includes('?'), 'Item cannot come before unassociated items');\n Assert(!after.includes(group), `Item cannot come after itself: ${group}`);\n Assert(!after.includes('?'), 'Item cannot come after unassociated items');\n\n if (!Array.isArray(nodes)) {\n nodes = [nodes];\n }\n\n for (const node of nodes) {\n const item = {\n seq: this._items.length,\n sort,\n before,\n after,\n group,\n node\n };\n\n this._items.push(item);\n }\n\n // Insert event\n\n if (!options.manual) {\n const valid = this._sort();\n Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');\n }\n\n return this.nodes;\n }\n\n merge(others) {\n\n if (!Array.isArray(others)) {\n others = [others];\n }\n\n for (const other of others) {\n if (other) {\n for (const item of other._items) {\n this._items.push(Object.assign({}, item)); // Shallow cloned\n }\n }\n }\n\n // Sort items\n\n this._items.sort(internals.mergeSort);\n for (let i = 0; i < this._items.length; ++i) {\n this._items[i].seq = i;\n }\n\n const valid = this._sort();\n Assert(valid, 'merge created a dependencies error');\n\n return this.nodes;\n }\n\n sort() {\n\n const valid = this._sort();\n Assert(valid, 'sort created a dependencies error');\n\n return this.nodes;\n }\n\n _sort() {\n\n // Construct graph\n\n const graph = {};\n const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives\n const groups = Object.create(null);\n\n for (const item of this._items) {\n const seq = item.seq; // Unique across all items\n const group = item.group;\n\n // Determine Groups\n\n groups[group] = groups[group] || [];\n groups[group].push(seq);\n\n // Build intermediary graph using 'before'\n\n graph[seq] = item.before;\n\n // Build second intermediary graph with 'after'\n\n for (const after of item.after) {\n graphAfters[after] = graphAfters[after] || [];\n graphAfters[after].push(seq);\n }\n }\n\n // Expand intermediary graph\n\n for (const node in graph) {\n const expandedGroups = [];\n\n for (const graphNodeItem in graph[node]) {\n const group = graph[node][graphNodeItem];\n groups[group] = groups[group] || [];\n expandedGroups.push(...groups[group]);\n }\n\n graph[node] = expandedGroups;\n }\n\n // Merge intermediary graph using graphAfters into final graph\n\n for (const group in graphAfters) {\n if (groups[group]) {\n for (const node of groups[group]) {\n graph[node].push(...graphAfters[group]);\n }\n }\n }\n\n // Compile ancestors\n\n const ancestors = {};\n for (const node in graph) {\n const children = graph[node];\n for (const child of children) {\n ancestors[child] = ancestors[child] || [];\n ancestors[child].push(node);\n }\n }\n\n // Topo sort\n\n const visited = {};\n const sorted = [];\n\n for (let i = 0; i < this._items.length; ++i) { // Looping through item.seq values out of order\n let next = i;\n\n if (ancestors[i]) {\n next = null;\n for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values\n if (visited[j] === true) {\n continue;\n }\n\n if (!ancestors[j]) {\n ancestors[j] = [];\n }\n\n const shouldSeeCount = ancestors[j].length;\n let seenCount = 0;\n for (let k = 0; k < shouldSeeCount; ++k) {\n if (visited[ancestors[j][k]]) {\n ++seenCount;\n }\n }\n\n if (seenCount === shouldSeeCount) {\n next = j;\n break;\n }\n }\n }\n\n if (next !== null) {\n visited[next] = true;\n sorted.push(next);\n }\n }\n\n if (sorted.length !== this._items.length) {\n return false;\n }\n\n const seqIndex = {};\n for (const item of this._items) {\n seqIndex[item.seq] = item;\n }\n\n this._items = [];\n this.nodes = [];\n\n for (const value of sorted) {\n const sortedItem = seqIndex[value];\n this.nodes.push(sortedItem.node);\n this._items.push(sortedItem);\n }\n\n return true;\n }\n};\n\n\ninternals.mergeSort = (a, b) => {\n\n return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);\n};\n","module.exports = clone;\n\n/*\n Deep clones all properties except functions\n\n var arr = [1, 2, 3];\n var subObj = {aa: 1};\n var obj = {a: 3, b: 5, c: arr, d: subObj};\n var objClone = clone(obj);\n arr.push(4);\n subObj.bb = 2;\n obj; // {a: 3, b: 5, c: [1, 2, 3, 4], d: {aa: 1}}\n objClone; // {a: 3, b: 5, c: [1, 2, 3], d: {aa: 1, bb: 2}}\n*/\n\nfunction clone(obj) {\n if (typeof obj == 'function') {\n return obj;\n }\n var result = Array.isArray(obj) ? [] : {};\n for (var key in obj) {\n // include prototype properties\n var value = obj[key];\n var type = {}.toString.call(value).slice(8, -1);\n if (type == 'Array' || type == 'Object') {\n result[key] = clone(value);\n } else if (type == 'Date') {\n result[key] = new Date(value.getTime());\n } else if (type == 'RegExp') {\n result[key] = RegExp(value.source, getRegExpFlags(value));\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction getRegExpFlags(regExp) {\n if (typeof regExp.source.flags == 'string') {\n return regExp.source.flags;\n } else {\n var flags = [];\n regExp.global && flags.push('g');\n regExp.ignoreCase && flags.push('i');\n regExp.multiline && flags.push('m');\n regExp.sticky && flags.push('y');\n regExp.unicode && flags.push('u');\n return flags.join('');\n }\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.14.0-beta.2';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","// Implement an LRU Cache\n\nclass LLNode {\n constructor(key, value, metadata = {}, prev = null, next = null) {\n this.key = key;\n this.value = value;\n this.metadata = metadata;\n this.prev = prev;\n this.next = next;\n }\n}\n\nclass LRUCache {\n constructor(max_size = 3) {\n this._max_size = max_size;\n this._cur_size = 0; // replace with map.size so we aren't managing manually?\n this._store = new Map();\n\n // Track LRU state\n this._head = null;\n this._tail = null;\n\n // Validate options\n if (max_size === null || max_size < 0) {\n throw new Error('Cache \"max_size\" must be >= 0');\n }\n }\n\n has(key) {\n // Check key membership without updating LRU\n return this._store.has(key);\n }\n\n get(key) {\n // Retrieve value from cache (if present) and update LRU cache accordingly\n const cached = this._store.get(key);\n if (!cached) {\n return null;\n }\n if (this._head !== cached) {\n // Rewrite the cached value to ensure it is head of the list\n this.add(key, cached.value);\n }\n return cached.value;\n }\n\n add(key, value, metadata = {}) {\n // Add an item. Forcibly replaces the existing cached value for the same key.\n if (this._max_size === 0) {\n // Don't cache items if cache has 0 size. Also prevent users from trying \"negative number for infinite cache\".\n return;\n }\n\n const prior = this._store.get(key);\n if (prior) {\n this._remove(prior);\n }\n\n const node = new LLNode(key, value, metadata, null, this._head);\n\n if (this._head) {\n this._head.prev = node;\n } else {\n this._tail = node;\n }\n\n this._head = node;\n this._store.set(key, node);\n\n if (this._max_size >= 0 && this._cur_size >= this._max_size) {\n const old = this._tail;\n this._tail = this._tail.prev;\n this._remove(old);\n }\n this._cur_size += 1;\n }\n\n\n // Cache manipulation methods\n clear() {\n this._head = null;\n this._tail = null;\n this._cur_size = 0;\n this._store = new Map();\n }\n\n // Public method, remove by key\n remove(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return false;\n }\n this._remove(cached);\n return true;\n }\n\n // Internal implementation, useful when working on list\n _remove(node) {\n if (node.prev !== null) {\n node.prev.next = node.next;\n } else {\n this._head = node.next;\n }\n\n if (node.next !== null) {\n node.next.prev = node.prev;\n } else {\n this._tail = node.prev;\n }\n this._store.delete(node.key);\n this._cur_size -= 1;\n }\n\n /**\n * Find a matching item in the cache, or return null. This is useful for \"approximate match\" semantics,\n * to check if something qualifies as a cache hit despite not having the exact same key.\n * (Example: zooming into a region, where the smaller region is a subset of something already cached)\n * @param callback\n * @returns {null|LLNode}\n */\n find(callback) {\n let node = this._head;\n while (node) {\n const next = node.next;\n if (callback(node)) {\n return node;\n }\n node = next;\n }\n }\n}\n\nexport { LRUCache };\n","import justclone from 'just-clone';\n\n/**\n * The \"just-clone\" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.\n * @param data\n * @returns {*}\n */\nfunction clone(data) {\n if (typeof data !== 'object') {\n return data;\n }\n return justclone(data);\n}\n\nexport { clone };\n","/**\n * Perform a series of requests, respecting order of operations\n */\n\nimport {Sorter} from '@hapi/topo';\n\n\nfunction _parse_declaration(spec) {\n // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.\n const parsed = /^(?\\w+)$|((?\\w+)+\\(\\s*(?[^)]+?)\\s*\\))/.exec(spec);\n if (!parsed) {\n throw new Error(`Unable to parse dependency specification: ${spec}`);\n }\n\n let {name_alone, name_deps, deps} = parsed.groups;\n if (name_alone) {\n return [name_alone, []];\n }\n\n deps = deps.split(/\\s*,\\s*/);\n return [name_deps, deps];\n}\n\nfunction getLinkedData(shared_options, entities, dependencies, consolidate = true) {\n if (!dependencies.length) {\n return [];\n }\n\n const parsed = dependencies.map((spec) => _parse_declaration(spec));\n const dag = new Map(parsed);\n\n // Define the order to perform requests in, based on a DAG\n const toposort = new Sorter();\n for (let [name, deps] of dag.entries()) {\n try {\n toposort.add(name, {after: deps, group: name});\n } catch (e) {\n throw new Error(`Invalid or possible circular dependency specification for: ${name}`);\n }\n }\n const order = toposort.nodes;\n\n // Verify that all requested entities exist by name!\n const responses = new Map();\n for (let name of order) {\n const provider = entities.get(name);\n if (!provider) {\n throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);\n }\n\n // Each promise should only be triggered when the things it depends on have been resolved\n const depends_on = dag.get(name) || [];\n const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));\n\n const this_result = prereq_promises.then((prior_results) => {\n // Each request will be told the name of the provider that requested it. This can be used during post-processing,\n // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)\n // This has a secondary effect: it ensures that any changes made to \"shared\" options in one adapter will\n // not leak out to others via a mutable shared object reference.\n const options = Object.assign({_provider_name: name}, shared_options);\n return provider.getData(options, ...prior_results);\n });\n responses.set(name, this_result);\n }\n return Promise.all([...responses.values()])\n .then((all_results) => {\n if (consolidate) {\n // Some usages- eg fetch + data join tasks- will only require the last response in the sequence\n // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)\n return all_results[all_results.length - 1];\n }\n return all_results;\n });\n}\n\n\nexport {getLinkedData};\n\n// For testing only\nexport {_parse_declaration};\n","/**\n * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.\n */\nimport { clone } from './util';\n\n\nfunction groupBy(records, group_key) {\n const result = new Map();\n for (let item of records) {\n const item_group = item[group_key];\n\n if (typeof item_group === 'undefined') {\n throw new Error(`All records must specify a value for the field \"${group_key}\"`);\n }\n if (typeof item_group === 'object') {\n // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)\n throw new Error('Attempted to group on a field with non-primitive values');\n }\n\n let group = result.get(item_group);\n if (!group) {\n group = [];\n result.set(item_group, group);\n }\n group.push(item);\n }\n return result;\n}\n\n\nfunction _any_match(type, left, right, left_key, right_key) {\n // Helper that consolidates logic for all three join types\n const right_index = groupBy(right, right_key);\n const results = [];\n for (let item of left) {\n const left_match_value = item[left_key];\n const right_matches = right_index.get(left_match_value) || [];\n if (right_matches.length) {\n // Record appears on both left and right; equiv to an inner join\n results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));\n } else if (type !== 'inner') {\n // Record appears on left but not right\n results.push(clone(item));\n }\n }\n\n if (type === 'outer') {\n // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side\n const left_index = groupBy(left, left_key);\n for (let item of right) {\n const right_match_value = item[right_key];\n const left_matches = left_index.get(right_match_value) || [];\n if (!left_matches.length) {\n results.push(clone(item));\n }\n }\n }\n return results;\n}\n\n/**\n * Equivalent to LEFT OUTER JOIN in SQL.\n * @param left\n * @param right\n * @param left_key\n * @param right_key\n * @returns {*[]}\n */\nfunction left_match(left, right, left_key, right_key) {\n return _any_match('left', ...arguments);\n}\n\nfunction inner_match(left, right, left_key, right_key) {\n return _any_match('inner', ...arguments);\n}\n\nfunction full_outer_match(left, right, left_key, right_key) {\n return _any_match('outer', ...arguments);\n}\n\nexport {left_match, inner_match, full_outer_match, groupBy};\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 1 }]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 2 }]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nimport {BaseUrlAdapter} from 'undercomplicate';\n\nimport {parseMarker} from '../helpers/parse';\n\n// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs.\n// Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal\n// methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the\n// private API methods exist in the base class.\n\n/**\n * Replaced with the BaseLZAdapter class.\n * @public\n * @deprecated\n */\nclass BaseAdapter {\n constructor() {\n throw new Error('The \"BaseAdapter\" and \"BaseApiAdapter\" classes have been replaced in LocusZoom 0.14. See migration guide for details.');\n }\n}\n\n/**\n * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @deprecated\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {}\n\n\n/**\n * @param {object} config\n * @param [config.cache_enabled=true]\n * @param [config.cache_size=3]\n * @param [config.url]\n * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name.\n * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant)\n * Typically, this is only disabled if the response payload is very unusual\n * @param {String[]} [limit_fields=null] If an API returns far more data than is needed, this can be used to simplify\n * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.\n */\nclass BaseLZAdapter extends BaseUrlAdapter {\n constructor(config = {}) {\n if (config.params) {\n // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places.\n console.warn('Deprecation warning: all options in \"config.params\" should now be specified as top level keys.');\n Object.assign(config, config.params || {});\n delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality!\n }\n super(config);\n\n // Prefix the namespace for this source to all fieldnames: id -> assoc.id\n // This is useful for almost all layers because the layout object says where to find every field, exactly.\n // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on\n // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear\n // in the response. (gene_name instead of genes.gene_name)\n const { prefix_namespace = true, limit_fields } = config;\n this._prefix_namespace = prefix_namespace;\n this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields\n }\n\n /**\n * Determine how a particular request will be identified in cache. Most LZ requests are region based,\n * so the default is a string concatenation of `chr_start_end`\n * @param options Receives plot.state plus any other request options defined by this source\n * @returns {string}\n * @private\n */\n _getCacheKey(options) {\n // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default\n let {chr, start, end} = options; // Current view: plot.state\n\n // Does a prior cache hit qualify as a superset of the ROI?\n const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end);\n if (superset) {\n ({ chr, start, end } = superset.metadata);\n }\n\n // The default cache key is region-based, and this method only returns the region-based part of the cache hit\n // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check\n options._cache_meta = { chr, start, end };\n return `${chr}_${start}_${end}`;\n }\n\n /**\n * Add the \"local namespace\" as a prefix for every field returned for this request. Eg if the association api\n * returns a field called variant, and the source is referred to as \"assoc\" within a particular data layer, then\n * the returned records will have a field called \"assoc:variant\"\n *\n * @param records\n * @param options\n * @returns {*}\n * @private\n */\n _postProcessResponse(records, options) {\n if (!this._prefix_namespace || !Array.isArray(records)) {\n return records;\n }\n\n // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for\n // assoc data might see \"variant\" as \"assoc.variant\"\n const { _limit_fields } = this;\n const { _provider_name } = options;\n\n return records.map((row) => {\n return Object.entries(row).reduce(\n (acc, [label, value]) => {\n // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload.\n if (!_limit_fields || _limit_fields.has(label)) {\n acc[`${_provider_name}:${label}`] = value;\n }\n return acc;\n },\n {}\n );\n });\n }\n\n /**\n * Convenience method, manually called in LZ sources that deal with dependent data.\n *\n * In the last step of fetching data, LZ adds a prefix to each field name.\n * This means that operations like \"build query based on prior data\" can't just ask for \"log_pvalue\" because\n * they are receiving \"assoc:log_pvalue\" or some such unknown prefix.\n *\n * This helper lets us use dependent data more easily. Not every adapter needs to use this method.\n *\n * @param {Object} a_record One record (often the first one in a set of records)\n * @param {String} fieldname The desired fieldname, eg \"log_pvalue\"\n */\n _findPrefixedKey(a_record, fieldname) {\n const suffixer = new RegExp(`:${fieldname}$`);\n const match = Object.keys(a_record).find((key) => suffixer.test(key));\n if (!match) {\n throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`);\n }\n return match;\n }\n}\n\n\n/**\n * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies\n * of one particular web server.\n */\nclass BaseUMAdapter extends BaseLZAdapter {\n /**\n * @param {Object} config\n * @param {String} [config.build] The genome build to be used by all requests for this adapter.\n */\n constructor(config = {}) {\n super(config);\n // The UM portaldev API accepts an (optional) parameter \"genome_build\"\n this._genome_build = config.genome_build || config.build;\n }\n\n _validateBuildSource(build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${this.constructor.name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`);\n }\n }\n\n // Special behavior for the UM portaldev API: col -> row format normalization\n /**\n * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.\n * @param response_text\n * @param options\n * @returns {Object[]}\n * @private\n */\n _normalizeResponse(response_text, options) {\n let data = super._normalizeResponse(...arguments);\n // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload\n data = data.data || data;\n\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the columns, and create an object for each row record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n}\n\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n *\n * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL\n */\nclass AssociationLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files\n const { source } = config;\n this._source_id = source;\n }\n\n _getURL (request_options) {\n const {chr, start, end} = request_options;\n const base = super._getURL(request_options);\n return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`;\n }\n}\n\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GwasCatalogLZ extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build] The genome build to use when requesting the specific genomic region.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n const source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given.\n // We will avoid transforming or modifying the payload.\n this._prefix_namespace = false;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GeneConstraintLZ extends BaseLZAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config = {}) {\n super(config);\n this._prefix_namespace = false;\n }\n\n _buildRequestOptions(state, genes_data) {\n const build = state.genome_build || this._config.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = new Set();\n for (let gene of genes_data) {\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n unique_gene_names.add(gene.gene_name);\n }\n\n state.query = [...unique_gene_names.values()].map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n state.build = build;\n return Object.assign({}, state);\n }\n\n _performRequest(options) {\n let {query, build} = options;\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears to set max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve([]);\n }\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n\n const url = this._getURL(options);\n\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.\n */\n _normalizeResponse(response_text) {\n if (typeof response_text !== 'string') {\n // If the query short-circuits, we receive an empty list instead of a string\n return response_text;\n }\n const data = JSON.parse(response_text);\n return data.data;\n }\n}\n\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant and yse that as the LD reference variant.\n *\n * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly\n * if this information is not provided.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass LDServer extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.method='rsquare'] The metric used to calculate LD\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n __find_ld_refvar(state, assoc_data) {\n const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant');\n const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue');\n\n // Determine the reference variant (via user selected OR automatic-per-track)\n let refvar;\n let best_hit = {};\n if (state.ldrefvar) {\n // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data\n refvar = state.ldrefvar;\n best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {};\n } else {\n // find highest log-value and associated var spec\n let best_logp = 0;\n for (let item of assoc_data) {\n const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item;\n if (log_pvalue > best_logp) {\n best_logp = log_pvalue;\n refvar = variant;\n best_hit = item;\n }\n }\n }\n\n // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting.\n // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase.\n best_hit.lz_is_ld_refvar = true;\n\n // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server,\n // the variant fields must be normalized to a specific format. All later LD operations will use that format.\n const match = parseMarker(refvar, true);\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n\n const [chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip?\n if (ref && alt) {\n refvar += `_${ref}/${alt}`;\n }\n\n const coord = +pos;\n // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably\n // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby.\n if ((coord && state.ldrefvar && state.chr) && (chrom !== state.chr || coord < state.start || coord > state.end)) {\n // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a\n // *copy* of plot.state, so wiping here doesn't remove the original value.\n state.ldrefvar = null;\n return this.__find_ld_refvar(state, assoc_data);\n }\n\n // Return the reference variant, in a normalized format suitable for LDServer queries\n return refvar;\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n // No variants, so no need to annotate association data with LD!\n // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region)\n base._skip_request = true;\n return base;\n }\n\n base.ld_refvar = this.__find_ld_refvar(state, assoc_data);\n\n // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config\n const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let ld_source = state.ld_source || this._config.source || '1000G';\n const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL\n\n if (ld_source === '1000G' && genome_build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n ld_source = '1000G-FRZ09';\n }\n\n this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option\n return Object.assign({}, base, { genome_build, ld_source, ld_population });\n }\n\n _getURL(request_options) {\n const method = this._config.method || 'rsquare';\n const {\n chr, start, end,\n ld_refvar,\n genome_build, ld_source, ld_population,\n } = request_options;\n\n const base = super._getURL(request_options);\n\n return [\n base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(ld_refvar),\n '&chrom=', encodeURIComponent(chr),\n '&start=', encodeURIComponent(start),\n '&stop=', encodeURIComponent(end),\n ].join('');\n }\n\n _getCacheKey(options) {\n // LD is keyed by more than just region; append other parameters to the base cache key\n const base = super._getCacheKey(options);\n const { ld_refvar, ld_source, ld_population } = options;\n return `${base}_${ld_refvar}_${ld_source}_${ld_population}`;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird.\n return Promise.resolve([]);\n }\n\n const url = this._getURL(options);\n\n // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseUMAdapter {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['position', 'recomb_rate'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {object} config.data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseLZAdapter {\n constructor(config = {}) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n super(...arguments);\n const { data } = config;\n if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key\n throw new Error(\"'StaticSource' must provide data as required option 'config.data'\");\n }\n this._data = data;\n }\n\n _performRequest(options) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will\n * be used to provide PheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const base = super._getURL(request_options);\n const url = [\n base,\n \"?filter=variant eq '\", encodeURIComponent(request_options.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n _getCacheKey(options) {\n // Not a region based source; don't do anything smart for cache check\n return this._getURL(options);\n }\n}\n\n// Deprecated symbols\nexport { BaseAdapter, BaseApiAdapter };\n\n// Usually used as a parent class for custom code\nexport { BaseLZAdapter, BaseUMAdapter };\n\n// Usually used as a standalone class\nexport {\n AssociationLZ,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","import {LRUCache} from './lru_cache';\nimport {clone} from './util';\n\nclass BaseAdapter {\n constructor(config = {}) {\n this._config = config;\n const {\n // Cache control\n cache_enabled = true,\n cache_size = 3,\n } = config;\n this._enable_cache = cache_enabled;\n this._cache = new LRUCache(cache_size);\n }\n\n _buildRequestOptions(options, dependent_data) {\n // Perform any pre-processing required that may influence the request. Receives an array with the payloads\n // for each request that preceded this one in the dependency chain\n // This method may optionally take dependent data into account\n return Object.assign({}, options);\n }\n\n _getCacheKey(options) {\n /* istanbul ignore next */\n if (this._enable_cache) {\n throw new Error('Method not implemented');\n }\n return null;\n }\n\n /**\n * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)\n * @param options\n * @returns {Promise}\n * @private\n */\n _performRequest(options) {\n /* istanbul ignore next */\n throw new Error('Not implemented');\n }\n\n _normalizeResponse(response_text, options) {\n // Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.\n return response_text;\n }\n\n /**\n * Perform custom client-side operations on the retrieved data. For example, add calculated fields or\n * perform rapid client-side filtering on cached data\n * @param records\n * @param {Object} options\n * @returns {*}\n * @private\n */\n _annotateRecords(records, options) {\n return records;\n }\n\n /**\n * A hook to transform the response after all operations are done. For example, this can be used to prefix fields\n * with a namespace unique to the request, like assoc.log_pvalue. (that way, annotations and validation can happen\n * on the actual API payload, without having to guess what the fields were renamed to).\n * @param records\n * @param options\n * @private\n */\n _postProcessResponse(records, options) {\n return records;\n }\n\n getData(options = {}, ...dependent_data) {\n // Public facing method to define, perform, and process the request\n options = this._buildRequestOptions(options, ...dependent_data);\n\n // Then retrieval and parse steps: parse + normalize response, annotate\n const cache_key = this._getCacheKey(options);\n\n let result;\n if (this._enable_cache && this._cache.has(cache_key)) {\n result = this._cache.get(cache_key);\n } else {\n // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)\n // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry\n // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,\n // even if the actual cache key wasn't an exact match\n result = Promise.resolve(this._performRequest(options))\n // Note: we cache the normalized (parsed) response\n .then((text) => this._normalizeResponse(text, options));\n this._cache.add(cache_key, result, options._cache_meta);\n // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request\n // Otherwise, temporary failures couldn't be resolved by trying again in a moment\n result.catch((e) => this._cache.remove(cache_key));\n }\n\n return result\n // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache\n .then((data) => clone(data))\n .then((records) => this._annotateRecords(records, options))\n .then((records) => this._postProcessResponse(records, options));\n }\n}\n\n\n/**\n * Fetch data over the web\n */\nclass BaseUrlAdapter extends BaseAdapter {\n constructor(config = {}) {\n super(config);\n this._url = config.url;\n }\n\n\n // Default cache key is the URL for the request.\n _getCacheKey(options) {\n return this._getURL(options);\n }\n\n _getURL(options) {\n return this._url;\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.\n // (not validating in constructor allows URL adapter to be used as more generic parent class)\n if (!this._url) {\n throw new Error('Web based resources must specify a resource URL as option \"url\"');\n }\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n // In most cases, we store the response as text so that the copy in cache is clean (no mutable references)\n return response.text();\n });\n }\n\n _normalizeResponse(response_text, options) {\n if (typeof response_text === 'string') {\n return JSON.parse(response_text);\n }\n // Some custom usages will return an object directly; return a copy of the object\n return response_text;\n }\n}\n\nexport { BaseAdapter, BaseUrlAdapter };\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import TRANSFORMS from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n // Two scenarios: we are requesting a field by full name, OR there are transforms to apply\n // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`\n const field_pattern = /^(?:\\w+:\\w+|^\\w+)(?:\\|\\w+)*$/;\n if (!field_pattern.test(field)) {\n throw new Error(`Invalid field specifier: '${field}'`);\n }\n\n const [name, ...transforms] = field.split('|');\n\n this.full_name = field; // fieldname + transforms\n this.field_name = name; // just fieldname\n this.transformations = transforms.map((name) => TRANSFORMS.get(name));\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (data[this.field_name] !== undefined) { // Fallback: value sans transforms\n val = data[this.field_name];\n } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations\n val = extra[this.field_name];\n } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {}\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * @module\n * @private\n */\nimport {getLinkedData} from 'undercomplicate';\n\nimport { DATA_OPS } from '../registry';\n\n\nclass DataOperation {\n /**\n * Perform a data operation (such as a join)\n * @param {String} join_type\n * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!\n * @param params Optional user/layout parameters to be passed to the data function\n */\n constructor(join_type, initiator, params) {\n this._callable = DATA_OPS.get(join_type);\n this._initiator = initiator;\n this._params = params || [];\n }\n\n getData(plot_state, ...dependent_recordsets) {\n // Most operations are joins: they receive two pieces of data (eg left + right)\n // Other ops are possible, like consolidating just one set of records to best value per key\n // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]\n\n // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options\n const context = {plot_state, data_layer: this._initiator};\n return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));\n }\n}\n\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a\n * designated order.\n *\n * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can\n * perform its own calculations, filter results, and apply transforms without influencing other layers.\n * (while still respecting a shared cache where appropriate)\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n /**\n * Parse the data layer configuration when a layer is first created.\n * Validate config, and return entities and dependencies in a format usable for data retrieval.\n * This is used by data layers, and also other data-retrieval functions (like subscribeToDate).\n *\n * Inherent assumptions:\n * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed.\n * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be\n * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the\n * removed adapter.\n * @param {Object} namespace_options\n * @param {Array} data_operations\n * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations,\n * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate\n * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care!\n * @returns {Array} Map of entities and list of dependencies\n */\n config_to_sources(namespace_options = {}, data_operations = [], initiator) {\n const entities = new Map();\n const namespace_local_names = Object.keys(namespace_options);\n\n // 1. Specify how to coordinate data. Precedence:\n // a) EXPLICIT fetch logic,\n // b) IMPLICIT auto-generate fetch order if there is only one NS,\n // c) Throw \"spec required\" error if > 1, because 2 adapters may need to be fetched in a sequence\n let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from}\n if (!dependency_order) {\n dependency_order = { type: 'fetch', from: namespace_local_names };\n data_operations.unshift(dependency_order);\n }\n\n // Validate that all NS items are available to the root requester in DataSources. All layers recognize a\n // default value, eg people copying the examples tend to have defined a datasource called \"assoc\"\n const ns_pattern = /^\\w+$/;\n for (let [local_name, global_name] of Object.entries(namespace_options)) {\n if (!ns_pattern.test(local_name)) {\n throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`);\n }\n\n const source = this._sources.get(global_name);\n if (!source) {\n throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`);\n }\n entities.set(local_name, source);\n\n // Note: Dependency spec checker will consider \"ld(assoc)\" to match a namespace called \"ld\"\n if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) {\n // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join.\n // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch\n // Thus the default behavior is \"fetch all namespaces as though they don't depend on anything.\n // If they depend on something, only then does \"data_ops[@type=fetch].from\" need to be mutated\n dependency_order.from.push(local_name);\n }\n }\n\n let dependencies = Array.from(dependency_order.from);\n\n // Now check all joins. Are namespaces valid? Are they requesting known data?\n for (let config of data_operations) {\n let {type, name, requires, params} = config;\n if (type !== 'fetch') {\n let namecount = 0;\n if (!name) {\n name = config.name = `join${namecount}`;\n namecount += 1;\n }\n\n if (entities.has(name)) {\n throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`);\n }\n requires.forEach((require_name) => {\n if (!entities.has(require_name)) {\n throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`);\n }\n });\n\n const task = new DataOperation(type, initiator, params);\n entities.set(name, task);\n dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB)\n }\n }\n return [entities, dependencies];\n }\n\n /**\n * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end)\n * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts.\n * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances\n * (things that implement a method getData).\n * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order\n * @returns {Promise}\n */\n getData(plot_state, entities, dependencies) {\n if (!dependencies.length) {\n return Promise.resolve([]);\n }\n // The last dependency (usually the last join operation) determines the last thing returned.\n return getLinkedData(plot_state, entities, dependencies, true);\n }\n}\n\n\nexport default Requester;\n\nexport {DataOperation as _JoinTask};\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import WIDGETS from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n const options = this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n this.addWidget(layout);\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown)\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Add a new widget to the toolbar.\n * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to:\n * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here.\n * - call widget.show() to ensure that the widget is initialized and rendered correctly\n * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default.\n * @param {Object} layout The layout object describing the desired widget\n * @returns {layout.type}\n */\n addWidget(layout) {\n try {\n const widget = WIDGETS.create(layout.type, layout, this);\n this.widgets.push(widget);\n return widget;\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 14,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n const layer_legend = this.parent.data_layers[id].layout.legend;\n if (Array.isArray(layer_legend)) {\n layer_legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape === 'ribbon') {\n // Color ribbons describe a series of color stops: small boxes of color across a continuous\n // scale. Drawn horizontally, or vertically, like:\n // [red | orange | yellow | green ] label\n // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way.\n const width = +element.width || 25;\n const height = +element.height || width;\n const is_horizontal = (element.orientation || 'vertical') === 'horizontal';\n let color_stops = element.color_stops;\n\n const all_elements = selector.append('g');\n const ribbon_group = all_elements.append('g');\n const axis_group = all_elements.append('g');\n let axis_offset = 0;\n if (element.tick_labels) {\n let range;\n if (is_horizontal) {\n range = [0, width * color_stops.length - 1]; // 1 px offset to align tick with inner borders\n } else {\n range = [height * color_stops.length - 1, 0];\n }\n const scale = d3.scaleLinear()\n .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode\n .range(range);\n const axis = (is_horizontal ? d3.axisTop : d3.axisRight)(scale)\n .tickSize(3)\n .tickValues(element.tick_labels)\n .tickFormat((v) => v);\n axis_group\n .call(axis)\n .attr('class', 'lz-axis');\n let bcr = axis_group.node().getBoundingClientRect();\n axis_offset = bcr.height;\n }\n if (is_horizontal) {\n // Shift axis down (so that tick marks aren't above the origin)\n axis_group\n .attr('transform', `translate(0, ${axis_offset})`);\n // Ribbon appears below axis\n ribbon_group\n .attr('transform', `translate(0, ${axis_offset})`);\n } else {\n // Vertical mode: Shift axis ticks to the right of the ribbon\n all_elements.attr('transform', 'translate(5, 0)');\n axis_group\n .attr('transform', `translate(${width}, 0)`);\n }\n\n if (!is_horizontal) {\n // Vertical mode: renders top -> bottom but scale is usually specified low..high\n color_stops = color_stops.slice();\n color_stops.reverse();\n }\n for (let i = 0; i < color_stops.length; i++) {\n const color = color_stops[i];\n const to_next_marking = is_horizontal ? `translate(${width * i}, 0)` : `translate(0, ${height * i})`;\n ribbon_group\n .append('rect')\n .attr('class', element.class || '')\n .attr('stroke', 'black')\n .attr('transform', to_next_marking)\n .attr('stroke-width', 0.5)\n .attr('width', width)\n .attr('height', height)\n .attr('fill', color)\n .call(applyStyles, element.style || {});\n }\n\n // Note: In vertical mode, it's usually easier to put the label above the legend as a separate marker\n // This is because the legend element label is drawn last (can't use it's size to position the ribbon, which is drawn first)\n if (!is_horizontal && element.label) {\n throw new Error('Legend labels not supported for vertical ribbons (use a separate legend item as text instead)');\n }\n // This only makes sense for horizontal labels.\n label_x = (width * color_stops.length + padding);\n label_y += axis_offset;\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset] The distance between the axis title and the axis. Use this to prevent\n * the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly.\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n if (typeof layout.id !== 'string' || !layout.id) {\n throw new Error('Panel layouts must specify \"id\"');\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this._layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this._state_id = this.id;\n this.state[this._state_id] = this.state[this._state_id] || {};\n } else {\n this.state = null;\n this._state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this._data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this._data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this._zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this._event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this._event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this._data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this._data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id]._layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n const target_layer = this.data_layers[id];\n if (!target_layer) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n target_layer.destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (target_layer.svg.container) {\n target_layer.svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(target_layer._layout_idx, 1);\n delete this.state[target_layer._state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id]._layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n const { cliparea } = this.layout;\n\n // Set and position the inner border, style if necessary\n const { margin } = this.layout;\n this.inner_border\n .attr('x', margin.left)\n .attr('y', margin.top)\n .attr('width', this.parent_plot.layout.width - (margin.left + margin.right))\n .attr('height', this.layout.height - (margin.top + margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n const axes_config = this.layout.axes;\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (axes_config.x.range) {\n base_x_range.start = axes_config.x.range.start || base_x_range.start;\n base_x_range.end = axes_config.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: cliparea.height, end: 0 };\n if (axes_config.y1.range) {\n base_y1_range.start = axes_config.y1.range.start || base_y1_range.start;\n base_y1_range.end = axes_config.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: cliparea.height, end: 0 };\n if (axes_config.y2.range) {\n base_y2_range.start = axes_config.y2.range.start || base_y2_range.start;\n base_y2_range.end = axes_config.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n let { _interaction } = this.parent;\n const current_drag = _interaction.dragging;\n if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (_interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = _interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = _interaction.zooming.center - margin.left - this.layout.origin.x;\n const offset_ratio = anchor / cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (current_drag) {\n switch (current_drag.method) {\n case 'background':\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n } else {\n anchor = current_drag.start_x - margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${current_drag.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y;\n ranges[y_shifted][1] = +current_drag.dragged_y;\n } else {\n anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3);\n ranges[y_shifted][0] = cliparea.height;\n ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent._interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n // Redefine b/c might have been changed during call to parent re-render\n _interaction = this.parent._interaction;\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this._zoom_timeout !== null) {\n clearTimeout(this._zoom_timeout);\n }\n this._zoom_timeout = setTimeout(() => {\n this.parent._interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this._initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((id) => {\n const axis = this.layout.axes[id];\n if (!Object.keys(axis).length || axis.render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n axis.render = false;\n } else {\n axis.render = true;\n axis.label = axis.label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n const layout = this.layout;\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n layout.height = Math.max(Math.round(+height), layout.min_height);\n }\n }\n layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0);\n layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', layout.height);\n }\n if (this._initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n const { cliparea, margin } = this.layout;\n if (!isNaN(top) && top >= 0) {\n margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (margin.top + margin.bottom > this.layout.height) {\n extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2);\n margin.top -= extra;\n margin.bottom -= extra;\n }\n if (margin.left + margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2);\n margin.left -= extra;\n margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n margin[m] = Math.max(margin[m], 0);\n });\n cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0);\n cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0);\n cliparea.origin.x = margin.left;\n cliparea.origin.y = margin.top;\n\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this._data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent._panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n const { parent } = this;\n const y_index = this.layout.y_index;\n if (parent._panel_ids_by_y_index[y_index - 1]) {\n parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1];\n parent._panel_ids_by_y_index[y_index - 1] = this.id;\n parent.applyPanelYIndexesToPanelLayouts();\n parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n const { _panel_ids_by_y_index } = this.parent;\n if (_panel_ids_by_y_index[this.layout.y_index + 1]) {\n _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1];\n _panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this._data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this._data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this._data_promises)\n .then(() => {\n this._initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([\\w+_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future.\n * @event data_from_layer\n * @property {object} data\n * @property {String} data.layer The fully qualified ID of the layer emitting this event\n * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum\n * element. It reflects all namespaces and data operations used by that layer.\n * @see event:any_lz_event\n */\n\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this._initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this._panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this._remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this._interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this._event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this._event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this._panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this._panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id]._layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer._layer_state;\n delete this.layout.state[layer._state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n const target_panel = this.panels[id];\n if (!target_panel) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this._panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n target_panel.loader.hide();\n target_panel.toolbar.destroy(true);\n target_panel.curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (target_panel.svg.container) {\n target_panel.svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(target_panel._layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id]._layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the\n * structure of the data, instead of just displaying something.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @listens event:data_from_layer\n * @param {Object} [opts] Options\n * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched.\n * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details.\n * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested,\n * this is usually required in order to specify dependency order and join operations. See data layer documentation for details.\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(opts, success_callback) {\n const { from_layer, namespace, data_operations, onerror } = opts;\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = onerror || function (err) {\n console.error('An error occurred while acting on an external callback', err);\n };\n\n if (from_layer) {\n // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places.\n const base_prefix = `${this.getBaseId()}.`;\n // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code.\n const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`;\n\n // Ensure that a valid layer exists to watch\n let is_valid_layer = false;\n for (let p of Object.values(this.panels)) {\n is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target);\n if (is_valid_layer) {\n break;\n }\n }\n if (!is_valid_layer) {\n throw new Error(`Could not subscribe to unknown data layer ${layer_target}`);\n }\n\n const listener = (eventData) => {\n if (eventData.data.layer !== layer_target) {\n // Same event name fires for many layers; only fire success cb for the one layer we want\n return;\n }\n try {\n success_callback(eventData.data.content, this);\n } catch (error) {\n error_callback(error);\n }\n };\n\n this.on('data_from_layer', listener);\n return listener;\n }\n\n // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of\n // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized\n // in connection to some other visualization)\n if (!namespace) {\n throw new Error(\"subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option\");\n }\n\n const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot.\n const listener = () => {\n try {\n // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely,\n // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame.\n this.lzd.getData(this.state, entities, dependencies)\n .then((new_data) => success_callback(new_data, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this._remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this._remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this._remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this._initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /**\n * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another,\n * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties,\n * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.\n * @public\n */\n mutateLayout() {\n Object.values(this.panels).forEach((panel) => {\n Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout());\n });\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n const { _interaction } = this;\n if (panel_id) {\n return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(_interaction.dragging || _interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this._panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this._initialized) {\n this._panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let panel of Object.values(this.panels)) {\n if (panel.layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_layout = panel.layout;\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel_layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel_layout.margin.right, 0);\n panel_layout.width += delta;\n panel_layout.margin.left = x_linked_margins.left;\n panel_layout.margin.right = x_linked_margins.right;\n panel_layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n const resize_listener = () => this.rescaleSVG();\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener\n // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed\n if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers\n const options = { root: document.documentElement, threshold: 0.9 };\n const observer = new IntersectionObserver((entries, observer) => {\n if (entries.some((entry) => entry.intersectionRatio > 0)) {\n this.rescaleSVG();\n }\n }, options);\n // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup\n observer.observe(this.container);\n }\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this._mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this._panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent._panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent._panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this._panel_boundaries.hide_timeout);\n this._panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this._panel_boundaries.hide_timeout = setTimeout(() => {\n this._panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this._mouse_guide.vertical.attr('x', -1);\n this._mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this._mouse_guide.vertical.attr('x', coords[0]);\n this._mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n const { _interaction } = this;\n if (_interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x;\n _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y;\n this.panels[_interaction.panel_id].render();\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this._initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this._interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n const { _interaction } = this;\n if (!_interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[_interaction.panel_id] != 'object') {\n this._interaction = {};\n return this;\n }\n const panel = this.panels[_interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel._data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (_interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (_interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (_interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(_interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this._interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} value value\n */\nconst if_value = (parameters, value) => {\n if (typeof value == 'undefined' || parameters.field_value !== value) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} value value\n * @returns {*}\n */\nconst numerical_bin = (parameters, value) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+value < prev || (+value >= prev && +value < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @function ordinal_cycle\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n *\n * @function stable_choice\n *\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, value) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return nullval;\n }\n if (+value <= parameters.breaks[0]) {\n return values[0];\n } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +value && breaks[idx] >= +value) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\n/**\n * Calculate the effect direction based on beta, or the combination of beta and standard error.\n * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.\n *\n * @function effect_direction\n * @param parameters\n * @param parameters.'+' The value to return if the effect direction is positive\n * @param parameters.'-' The value to return if the effect direction is positive\n * @param parameters.beta_field The name of the field containing beta\n * @param parameters.stderr_beta_field The name of the field containing stderr_beta\n * @param {Object} input This function should receive the entire datum object, rather than one single field\n * @returns {null}\n */\nfunction effect_direction(parameters, input) {\n if (input === undefined) {\n return null;\n }\n\n const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters;\n\n if (!beta_field || !stderr_beta_field) {\n throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`);\n }\n\n const beta_val = input[beta_field];\n const se_val = input[stderr_beta_field];\n\n if (beta_val !== undefined) {\n if (se_val !== undefined) {\n if ((beta_val - 1.96 * se_val) > 0) {\n return plus_result;\n } else if ((beta_val + 1.96 * se_val) < 0) {\n return neg_result || null;\n }\n } else {\n if (beta_val > 0) {\n return plus_result;\n } else if (beta_val < 0) {\n return neg_result;\n }\n }\n }\n // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid\n // about expected data formats for layouts.\n return null;\n}\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, findFields, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n/**\n * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.\n * @property {module:LocusZoom_DataFunctions|'fetch'} type\n * @property {String[]} [from] For operations of type \"fetch\", this is required. By default, it will fill in any items provided in \"namespace\" (everything specified in namespace triggers an adapter/network request)\n * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace).\n * Eg, for ld to be fetched after association data, specify \"ld(assoc)\". Most LocusZoom examples fill in all items, in order to make the examples more clear.\n * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation.\n * Eg, if the retrieved data is combined via several left joins in series: `name: \"assoc_plus_ld\"` -> feeds into `{name: \"final\", requires: [\"assoc_plus_ld\"]}`\n * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just\n * the names of other namespaces (or data operations) whose results must be available before a join can be performed\n * @property {String[]} params Any user-defined parameters that should be passed to the particular join function\n * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']`\n * Separate from this section, data functions will also receive a copy of \"plot.state\" automatically.\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or \"line\" for a path.\n * A special \"ribbon\" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD)\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {'vertical'|'horizontal'} [orientation='vertical'] For shape \"ribbon\", specifies whether to draw the ribbon vertically or horizontally.\n * @property {Array} [tick_labels] For shape \"ribbon\", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops.\n * @property {String[]} [color_stops] For shape \"ribbon\", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels.\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n namespace: {},\n data_operations: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is\n * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely\n * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is\n * your job to assure that all of the expected fields are present in every element)\n * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data (\"assoc\")\n * to the globally unique name for something defined in LocusZoom.DataSources.\n * Namespaces allow a single layout to be reused to plot many tracks of the same type: \"given some form of association data, plot it\".\n * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse\n * a layout with a new provider of data- like plotting two association studies stacked together-\n * only the namespace section of the layout needs to be overridden.\n * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})`\n * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions})\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this._layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this._state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this._layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this._tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this._global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n\n // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval\n this._data_contract = new Set(); // List of all fields requested by the layout\n this._entities = new Map();\n this._dependencies = [];\n this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout\n }\n\n /****** Public interface: methods for manipulating the layer from other parts of LZ */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index + 1]) {\n layer_order[current_index] = layer_order[current_index + 1];\n layer_order[current_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index - 1]) {\n layer_order[current_index] = layer_order[current_index - 1];\n layer_order[current_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this._layer_state.extra_fields[id]) {\n this._layer_state.extra_fields[id] = {};\n }\n this._layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /**\n * A list of operations that should be run when the layout is mutated\n * Typically, these are things done once when a layout is first specified, that would not automatically\n * update when the layout was changed.\n * @public\n */\n mutateLayout() {\n // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract.\n if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester\n const { namespace, data_operations } = this.layout;\n this._data_contract = findFields(this.layout, Object.keys(namespace));\n const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this);\n this._entities = entities;\n this._dependencies = dependencies;\n }\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n *\n * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that\n * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data),\n * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}`\n * @protected\n * @param {Object} element The data associated with a particular element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression\n const id_field = this.layout.id_field;\n let value = element[id_field];\n if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) {\n // No field value was found directly, but if it looks like a template expression, next, try parsing that\n // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot!\n value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient\n }\n if (value === null || value === undefined) {\n // Neither exact field nor template options produced an ID\n throw new Error('Unable to generate element ID');\n }\n const element_id = value.toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Abstract method. It should be overridden by data layers that implement separate status\n * nodes, such as genes or intervals.\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or\n * a group of unrelated intervals (all markings grouped within a category).\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n\n // Does the data from the API satisfy the list of fields expected by this layout?\n // Not every record will have every possible field (example: left joins like assoc + ld). The check is \"did\n // we see this field at least once in any record at all\".\n if (this.data.length && this._data_contract.size) {\n const fields_unseen = new Set(this._data_contract);\n for (let record of this.data) {\n Object.keys(record).forEach((field) => fields_unseen.delete(field));\n if (!fields_unseen.size) {\n // Once every requested field has been seen in at least one record, no need to look at more records\n break;\n }\n }\n if (fields_unseen.size) {\n // Current implementation is a soft warning, so that certain \"incremental enhancement\" features\n // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info.\n // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data.\n console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in \"data_operations\" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`);\n }\n }\n\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value);\n }\n\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this._layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const _layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = _layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this._state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this._state_id] = _layer_state;\n }\n this._layer_state = _layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this._tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this._tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this._layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this._tooltips[id].selector.html('');\n this._tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this._tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this._tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this._tooltips[id]) {\n if (typeof this._tooltips[id].selector == 'object') {\n this._tooltips[id].selector.remove();\n }\n delete this._tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this._layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this._tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this._tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this._tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this._tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n const tooltip_layout = this.layout.tooltip;\n if (typeof tooltip_layout != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof tooltip_layout.show == 'string') {\n show_directive = { and: [ tooltip_layout.show ] };\n } else if (typeof tooltip_layout.show == 'object') {\n show_directive = tooltip_layout.show;\n }\n\n let hide_directive = {};\n if (typeof tooltip_layout.hide == 'string') {\n hide_directive = { and: [ tooltip_layout.hide ] };\n } else if (typeof tooltip_layout.hide == 'object') {\n hide_directive = tooltip_layout.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const _layer_state = this._layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (_layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this._layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this._layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this._layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this._layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self._state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies)\n .then((new_data) => {\n this.data = new_data;\n this.applyDataMethods();\n this._initialized = true;\n // Allow listeners (like subscribeToData) to see the information associated with a layer\n this.parent.emit(\n 'data_from_layer',\n { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin\n true\n );\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 15,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n // FIXME: Make gene text font sizes scalable\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n tooltip: null,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this._global_statuses).forEach((global_status) => {\n if (this._global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n data_layer._label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this._label_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer._label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer._label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer._label_texts.nodes();\n data_layer._label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this._label_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this._label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this._label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this._label_texts) {\n this._label_texts.remove();\n }\n\n this._label_texts = this._label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this._label_lines) {\n this._label_lines.remove();\n }\n this._label_lines = this._label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this._label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this._label_texts) {\n this._label_texts.remove();\n }\n if (this._label_lines) {\n this._label_lines.remove();\n }\n if (this._label_groups) {\n this._label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this._label_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{assoc:variant|htmlescape}}
    \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
    \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
    `,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

    {{gene_name|htmlescape}}

    '\n + 'Gene ID: {{gene_id|htmlescape}}
    '\n + 'Transcript ID: {{transcript_id|htmlescape}}
    '\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
    ConstraintExpected variantsObserved variantsConst. Metric
    Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
    o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
    Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
    o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
    pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
    o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

    {{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{catalog:variant|htmlescape}}
    '\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
    '\n + 'Top Trait: {{catalog:trait|htmlescape}}
    '\n + 'Top P Value: {{catalog:log_pvalue|logtoscinotation}}
    '\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
    ' +\n '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
    ' +\n 'Promoter
    ' +\n '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
    ' +\n '{{#if access:target}}Target: {{access:target|htmlescape}}
    {{/if}}' +\n 'Score: {{access:score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n id: 'recombrate',\n namespace: { 'recomb': 'recomb' },\n data_operations: [\n { type: 'fetch', from: ['recomb'] },\n ],\n type: 'line',\n tag: 'recombination',\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: 'recomb:position',\n },\n y_axis: {\n axis: 2,\n field: 'recomb:recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)'],\n },\n {\n type: 'left_match',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'ld'],\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n id_field: 'assoc:variant',\n coalesce: {\n active: true,\n },\n point_shape: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 'diamond',\n },\n },\n {\n // Not every dataset will provide these params\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'assoc:beta',\n stderr_beta_field: 'assoc:se',\n },\n },\n 'circle',\n ],\n point_size: {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: 'ld:correlation',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8'\n {\n shape: 'ribbon',\n orientation: 'vertical',\n width: 10,\n height: 15,\n color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0],\n },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: 'assoc:position',\n },\n y_axis: {\n axis: 1,\n field: 'assoc:log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n namespace: { 'access': 'access' },\n data_operations: [\n { type: 'fetch', from: ['access'] },\n ],\n match: { send: 'access:target', receive: 'access:target' },\n // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.\n id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',\n filters: [\n { field: 'access:score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: 'access:start1',\n field2: 'access:start2',\n },\n y_axis: {\n axis: 1,\n field: 'access:score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n\n base.data_operations.push({\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_catalog',\n requires: ['assoc_plus_ld', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n });\n\n base.tooltip.html += '{{#if catalog:rsid}}
    See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n return base;\n}();\n\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n namespace: { 'phewas': 'phewas' },\n data_operations: [\n { type: 'fetch', from: ['phewas'] },\n ],\n point_shape: [\n {\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'phewas:beta',\n stderr_beta_field: 'phewas:se',\n },\n },\n 'circle',\n ],\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',\n x_axis: {\n field: 'lz_auto_x', // Automatically added by the category_scatter layer\n category_field: 'phewas:trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: 'phewas:log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: 'phewas:trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Trait: {{phewas:trait_label|htmlescape}}
    \nTrait Category: {{phewas:trait_group|htmlescape}}
    \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
    β: {{phewas:beta|scinotation|htmlescape}}
    {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}`,\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{phewas:trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: 'phewas:log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n data_operations: [\n {\n type: 'fetch',\n from: ['gene', 'constraint(gene)'],\n },\n {\n name: 'gene_constraint',\n type: 'genes_to_gnomad_constraint',\n requires: ['gene', 'constraint'],\n },\n ],\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n data_operations: [\n {\n type: 'fetch', from: ['assoc', 'catalog'],\n },\n {\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n },\n ],\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: '#0000CC',\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'catalog:rsid', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n }\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 300,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 46,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 75, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 40,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{catalog:trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: 'catalog:trait', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: 'ld:correlation', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '12px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 55, bottom: 20, left: 70 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu)\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 55, bottom: 120, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 55, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel)\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or applying namespaces.\n let base = super.get(type).get(name);\n\n // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.\n // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)\n const custom_namespaces = overrides.namespace;\n if (!base.namespace) {\n // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout\n // NOTE: The \"merge namespace\" behavior means that data layers can add new data easily, but this method\n // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).\n delete overrides.namespace;\n }\n let result = merge(overrides, base);\n\n if (custom_namespaces) {\n result = applyNamespaces(result, custom_namespaces);\n }\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n\n // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested\n // from external sources. This is purely a hint, because not every layout is generated through the registry.\n if (type === 'data_layer' && copy.namespace) {\n copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();\n }\n\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","/**\n * \"Data operation\" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results\n *\n * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation\n * is a \"join\", such as combining association + LD together into a single set of records for plotting. Several join\n * functions (that operate by analogy to SQL) are provided built-in.\n *\n * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):\n * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.\n * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,\n * this is the recommended path to do so)\n * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),\n * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,\n * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks\n * (PheWAS), automatic panel legends or color schemes (BED tracks), etc.\n *\n * Usually, a data operation receives two recordsets (the left and right members of the join, like \"assoc\" and \"ld\").\n * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network\n * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is\n * uncommon. (if possible, try to provide your data with fewer adapters/network requests!)\n *\n * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,\n * particularly for advanced features, may carry assumptions about field names/ formatting.\n * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,\n * or it may match two datasets based on a specific way of identifying the variant)\n *\n * @module LocusZoom_DataFunctions\n */\nimport {joins} from 'undercomplicate';\n\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"data join\" functions.\n * @alias module:LocusZoom~DataFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\nfunction _wrap_join(handle) {\n // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).\n\n // Many of our join functions are implemented with a different number of arguments than what a datafunction\n // actually receives. (eg, a join function is generic and doesn't care about \"context\" information like plot.state)\n // This wrapper is simple shared code to handle required validation and conversion stuff.\n return (context, deps, ...params) => {\n if (deps.length !== 2) {\n throw new Error('Join functions must receive exactly two recordsets');\n }\n return handle(...deps, ...params);\n };\n}\n\n// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to\n// pick the most significant claim in the catalog for a variant, rather than joining every possible match.\n// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.\nfunction assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {\n if (!assoc_data.length) {\n return assoc_data;\n }\n\n // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each\n const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);\n\n const catalog_flat = []; // Store only the top significant claim for each catalog variant entry\n for (let claims of catalog_by_variant.values()) {\n // Find max item within this set of claims, push that to catalog_\n let best = 0;\n let best_variant;\n for (let item of claims) {\n const val = item[catalog_logp_name];\n if ( val >= best) {\n best_variant = item;\n best = val;\n }\n }\n best_variant.n_catalog_matches = claims.length;\n catalog_flat.push(best_variant);\n }\n return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);\n}\n\n// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.\nfunction genes_to_gnomad_constraint(genes_data, constraint_data) {\n genes_data.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return genes_data;\n}\n\n\n/**\n * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset\n *\n * @function\n * @name left_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('left_match', _wrap_join(joins.left_match));\n\n/**\n * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.\n *\n * @function\n * @name inner_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('inner_match', _wrap_join(joins.inner_match));\n\n/**\n * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.\n *\n * @function\n * @name full_outer_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('full_outer_match', _wrap_join(joins.full_outer_match));\n\n/**\n * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.\n *\n * @function\n * @name assoc_to_gwas_catalog\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: assoc records, then catalog records\n * @param {String} assoc_key The name of the key field in association data, eg variant ID\n * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID\n * @param {String} catalog_log_p_name The name of the \"log_pvalue\" field in gwas catalog data, used to choose the most significant claim for a given variant\n */\nregistry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));\n\n/**\n * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).\n *\n * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.\n *\n * @function\n * @name genes_to_gnomad_constraint\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data\n */\nregistry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));\n\nexport default registry;\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n DATA_OPS as DataFunctions,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n WIDGETS as Widgets,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n DataFunctions,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/docs/api/Panel.html b/docs/api/Panel.html index 8aa900fa..8f7ef055 100644 --- a/docs/api/Panel.html +++ b/docs/api/Panel.html @@ -1288,7 +1288,8 @@
    Parameters:
    - +

    The distance between the axis title and the axis. Use this to prevent +the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly.

    @@ -2082,7 +2083,7 @@
    Type:
    Source:
    @@ -2157,7 +2158,7 @@
    Type:
    Source:
    @@ -2229,7 +2230,7 @@
    Type:
    Source:
    @@ -2297,7 +2298,7 @@
    Type:
    Source:
    @@ -2369,7 +2370,7 @@
    Type:
    Source:
    @@ -2441,7 +2442,7 @@
    Type:
    Source:
    @@ -2509,7 +2510,7 @@
    Type:
    Source:
    @@ -2580,7 +2581,7 @@
    Type:
    Source:
    @@ -2651,7 +2652,7 @@
    Type:
    Source:
    @@ -2723,7 +2724,7 @@
    Type:
    Source:
    @@ -3030,7 +3031,7 @@
    Parameters:
    Source:
    @@ -3385,7 +3386,7 @@
    Parameters:
    Source:
    @@ -3590,7 +3591,7 @@
    Parameters:
    Source:
    @@ -3772,7 +3773,7 @@
    Parameters:
    Source:
    @@ -4327,7 +4328,7 @@
    Properties
    Source:
    diff --git a/docs/api/components_data_layer_base.js.html b/docs/api/components_data_layer_base.js.html index 2df4c105..5e3152e5 100644 --- a/docs/api/components_data_layer_base.js.html +++ b/docs/api/components_data_layer_base.js.html @@ -100,7 +100,8 @@

    Source: components/data_layer/base.js

    * @typedef {object} LegendItem * @property [shape] This is optional (e.g. a legend element could just be a textual label). * Supported values are the standard d3 3.x symbol types (i.e. "circle", "cross", "diamond", "square", - * "triangle-down", and "triangle-up"), as well as "rect" for an arbitrary square/rectangle or line for a path. + * "triangle-down", and "triangle-up"), as well as "rect" for an arbitrary square/rectangle or "line" for a path. + * A special "ribbon" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD) * @property {string} color The point color (hexadecimal, rgb, etc) * @property {string} label The human-readable label of the legend item * @property {string} [class] The name of a CSS class used to style the point in the legend @@ -109,9 +110,12 @@

    Source: components/data_layer/base.js

    * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element * if the value of the shape parameter is "line". * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if - * the value of the shape parameter is "rect". + * the value of the shape parameter is "rect" or "ribbon". * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if - * the value of the shape parameter is "rect". + * the value of the shape parameter is "rect" or "ribbon". + * @property {'vertical'|'horizontal'} [orientation='vertical'] For shape "ribbon", specifies whether to draw the ribbon vertically or horizontally. + * @property {Array} [tick_labels] For shape "ribbon", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops. + * @property {String[]} [color_stops] For shape "ribbon", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels. * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of * the legend element. */ diff --git a/docs/api/components_data_layer_genes.js.html b/docs/api/components_data_layer_genes.js.html index e3b46b87..14f58ec4 100644 --- a/docs/api/components_data_layer_genes.js.html +++ b/docs/api/components_data_layer_genes.js.html @@ -39,7 +39,7 @@

    Source: components/data_layer/genes.js

    // Optionally specify different fill and stroke properties stroke: 'rgb(54, 54, 150)', color: '#363696', - label_font_size: 12, + label_font_size: 15, label_exon_spacing: 3, exon_height: 10, bounding_box_padding: 3, @@ -294,6 +294,7 @@

    Source: components/data_layer/genes.js

    const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary') .data([gene], (d) => `${d.gene_name}_boundary`); + // FIXME: Make gene text font sizes scalable height = 1; boundaries.enter() .append('rect') diff --git a/docs/api/components_legend.js.html b/docs/api/components_legend.js.html index ce64449e..b8562c8a 100644 --- a/docs/api/components_legend.js.html +++ b/docs/api/components_legend.js.html @@ -46,7 +46,7 @@

    Source: components/legend.js

    width: 10, height: 10, padding: 5, - label_size: 12, + label_size: 14, hidden: false, }; @@ -128,11 +128,12 @@

    Source: components/legend.js

    let y = padding; let line_height = 0; this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => { - if (Array.isArray(this.parent.data_layers[id].layout.legend)) { - this.parent.data_layers[id].layout.legend.forEach((element) => { + const layer_legend = this.parent.data_layers[id].layout.legend; + if (Array.isArray(layer_legend)) { + layer_legend.forEach((element) => { const selector = this.elements_group.append('g') .attr('transform', `translate(${x}, ${y})`); - const label_size = +element.label_size || +this.layout.label_size || 12; + const label_size = +element.label_size || +this.layout.label_size; let label_x = 0; let label_y = (label_size / 2) + (padding / 2); line_height = Math.max(line_height, label_size + padding); @@ -163,6 +164,82 @@

    Source: components/legend.js

    label_x = width + padding; line_height = Math.max(line_height, height + padding); + } else if (shape === 'ribbon') { + // Color ribbons describe a series of color stops: small boxes of color across a continuous + // scale. Drawn horizontally, or vertically, like: + // [red | orange | yellow | green ] label + // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way. + const width = +element.width || 25; + const height = +element.height || width; + const is_horizontal = (element.orientation || 'vertical') === 'horizontal'; + let color_stops = element.color_stops; + + const all_elements = selector.append('g'); + const ribbon_group = all_elements.append('g'); + const axis_group = all_elements.append('g'); + let axis_offset = 0; + if (element.tick_labels) { + let range; + if (is_horizontal) { + range = [0, width * color_stops.length - 1]; // 1 px offset to align tick with inner borders + } else { + range = [height * color_stops.length - 1, 0]; + } + const scale = d3.scaleLinear() + .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode + .range(range); + const axis = (is_horizontal ? d3.axisTop : d3.axisRight)(scale) + .tickSize(3) + .tickValues(element.tick_labels) + .tickFormat((v) => v); + axis_group + .call(axis) + .attr('class', 'lz-axis'); + let bcr = axis_group.node().getBoundingClientRect(); + axis_offset = bcr.height; + } + if (is_horizontal) { + // Shift axis down (so that tick marks aren't above the origin) + axis_group + .attr('transform', `translate(0, ${axis_offset})`); + // Ribbon appears below axis + ribbon_group + .attr('transform', `translate(0, ${axis_offset})`); + } else { + // Vertical mode: Shift axis ticks to the right of the ribbon + all_elements.attr('transform', 'translate(5, 0)'); + axis_group + .attr('transform', `translate(${width}, 0)`); + } + + if (!is_horizontal) { + // Vertical mode: renders top -> bottom but scale is usually specified low..high + color_stops = color_stops.slice(); + color_stops.reverse(); + } + for (let i = 0; i < color_stops.length; i++) { + const color = color_stops[i]; + const to_next_marking = is_horizontal ? `translate(${width * i}, 0)` : `translate(0, ${height * i})`; + ribbon_group + .append('rect') + .attr('class', element.class || '') + .attr('stroke', 'black') + .attr('transform', to_next_marking) + .attr('stroke-width', 0.5) + .attr('width', width) + .attr('height', height) + .attr('fill', color) + .call(applyStyles, element.style || {}); + } + + // Note: In vertical mode, it's usually easier to put the label above the legend as a separate marker + // This is because the legend element label is drawn last (can't use it's size to position the ribbon, which is drawn first) + if (!is_horizontal && element.label) { + throw new Error('Legend labels not supported for vertical ribbons (use a separate legend item as text instead)'); + } + // This only makes sense for horizontal labels. + label_x = (width * color_stops.length + padding); + label_y += axis_offset; } else if (shape_factory) { // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.) const size = +element.size || 40; diff --git a/docs/api/components_panel.js.html b/docs/api/components_panel.js.html index 14d82a63..8be9fbf7 100644 --- a/docs/api/components_panel.js.html +++ b/docs/api/components_panel.js.html @@ -131,7 +131,8 @@

    Source: components/panel.js

    * genomic coordinates, eg 23423456 => 23.42 (Mb) * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated) * @param {string} [layout.axes.y1.label] Label text for the provided axis - * @param {number} [layout.axes.y1.label_offset] + * @param {number} [layout.axes.y1.label_offset] The distance between the axis title and the axis. Use this to prevent + * the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly. * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated) * @param {string} [layout.axes.y2.label] Label text for the provided axis @@ -461,7 +462,6 @@

    Source: components/panel.js

    * @returns {BaseDataLayer} */ addDataLayer(layout) { - // Sanity checks if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) { throw new Error('Invalid data layer layout'); @@ -1016,7 +1016,6 @@

    Source: components/panel.js

    * @returns {Panel} */ initialize() { - // Append a container group element to house the main panel group element and the clip path // Position with initial layout parameters const base_id = this.getBaseId(); @@ -1342,7 +1341,6 @@

    Source: components/panel.js

    * @returns {Panel} */ renderAxis(axis) { - if (!['x', 'y1', 'y2'].includes(axis)) { throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`); } diff --git a/docs/api/ext_lz-credible-sets.js.html b/docs/api/ext_lz-credible-sets.js.html index d5127304..cafaf322 100644 --- a/docs/api/ext_lz-credible-sets.js.html +++ b/docs/api/ext_lz-credible-sets.js.html @@ -282,7 +282,7 @@

    Source: ext/lz-credible-sets.js

    title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } }, min_height: 50, height: 50, - margin: { top: 25, right: 50, bottom: 10, left: 50 }, + margin: { top: 25, right: 50, bottom: 10, left: 70 }, inner_border: 'rgb(210, 210, 210)', toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel'), axes: { diff --git a/docs/api/ext_lz-intervals-enrichment.js.html b/docs/api/ext_lz-intervals-enrichment.js.html index 3197fee4..bc620e2d 100644 --- a/docs/api/ext_lz-intervals-enrichment.js.html +++ b/docs/api/ext_lz-intervals-enrichment.js.html @@ -187,7 +187,7 @@

    Source: ext/lz-intervals-enrichment.js

    hide: { and: ['unhighlighted', 'unselected'] }, html: `<b>Tissue</b>: {{intervals:tissueId|htmlescape}}<br> <b>Range</b>: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}<br> - <b>-log10 p</b>: {{intervals:pValue|neglog10|scinotation|htmlescape}}<br> + <b>-log<sub>10</sub> p</b>: {{intervals:pValue|neglog10|scinotation|htmlescape}}<br> <b>Enrichment (n-fold)</b>: {{intervals:fold|scinotation|htmlescape}}`, }; @@ -285,18 +285,18 @@

    Source: ext/lz-intervals-enrichment.js

    tag: 'intervals_enrichment', min_height: 250, height: 250, - margin: { top: 35, right: 50, bottom: 40, left: 50 }, + margin: { top: 35, right: 50, bottom: 40, left: 70 }, inner_border: 'rgb(210, 210, 210)', axes: { x: { label: 'Chromosome {{chr}} (Mb)', - label_offset: 32, + label_offset: 34, tick_format: 'region', extent: 'state', }, y1: { label: 'enrichment (n-fold)', - label_offset: 28, + label_offset: 40, }, }, interaction: { diff --git a/docs/api/ext_lz-intervals-track.js.html b/docs/api/ext_lz-intervals-track.js.html index ecc5e0ae..2f85b101 100644 --- a/docs/api/ext_lz-intervals-track.js.html +++ b/docs/api/ext_lz-intervals-track.js.html @@ -749,7 +749,7 @@

    Source: ext/lz-intervals-track.js

    tag: 'intervals', min_height: 50, height: 50, - margin: { top: 25, right: 150, bottom: 5, left: 50 }, + margin: { top: 25, right: 150, bottom: 5, left: 70 }, toolbar: (function () { const l = LocusZoom.Layouts.get('toolbar', 'standard_panel'); l.widgets.push({ diff --git a/docs/api/helpers_scalable.js.html b/docs/api/helpers_scalable.js.html index 356e3845..782a628b 100644 --- a/docs/api/helpers_scalable.js.html +++ b/docs/api/helpers_scalable.js.html @@ -264,9 +264,9 @@

    Source: helpers/scalable.js

    if (beta_val !== undefined) { if (se_val !== undefined) { - if ((beta_val - 2 * se_val) > 0) { + if ((beta_val - 1.96 * se_val) > 0) { return plus_result; - } else if ((beta_val + 2 * se_val) < 0) { + } else if ((beta_val + 1.96 * se_val) < 0) { return neg_result || null; } } else { diff --git a/docs/api/index.html b/docs/api/index.html index 4f7385c9..d5c1b3a5 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -45,14 +45,14 @@

    LocusZoom

    LocusZoom is a Javascript/d3 embeddable plugin for interactively visualizing statistical genetic data from customizable sources.

    -

    This is a low level library aimed at developers who want to customize their own data sharing/visualization. If you are a genetics researcher who just wants to make a fast visualization of your research results, try our user-friendly plot-your-own data services built on LocusZoom.js: my.locuszoom.org and LocalZoom.

    +

    This is a low level library aimed at developers who want to customize their own data sharing/visualization tools. If you are a genetics researcher who just wants to make a fast visualization of your research results, try our user-friendly plot-your-own data services built on LocusZoom.js: my.locuszoom.org and LocalZoom.

    Build Status

    See https://statgen.github.io/locuszoom/docs/ for full documentation and API reference.

    To see functional examples of plots generated with LocusZoom.js see statgen.github.io/locuszoom and statgen.github.io/locuszoom/#examples.

    -

    LocusZoom.js Standard Association Plot

    +

    LocusZoom.js Standard Association Plot

    Making a LocusZoom Plot

    1. Include Necessary JavaScript and CSS

    -

    The page you build that embeds the LocusZoom plugin must include the following resources, found in the dist directory:

    +

    The page you build that embeds the LocusZoom plugin must include the following resources, found in the dist directory (or via CDN):

    • d3.js
      @@ -112,15 +112,15 @@

      4. Put it Together with LocusZoom.populate()

      A basic example may then look like this:

      <html>
         <head>
      -    <script src="locuszoom.app.min.js" type="text/javascript"></script>
      -    <link rel="stylesheet" type="text/css" href="locuszoom.css"/>
      +    <script src="dist/locuszoom.app.min.js" type="text/javascript"></script>
      +    <link rel="stylesheet" type="text/css" href="dist/locuszoom.css"/>
         </head>
         <body>
           <div id="lz-plot"></div>
           <script type="text/javascript">
      -      var data_sources = new LocusZoom.DataSources();
      +      const data_sources = new LocusZoom.DataSources();
             data_sources.add("assoc", ["AssociationLZ", { url: "https://server.com/api/single/", source: 1 }]);
      -      var layout = {
      +      const layout = {
               width: 800,
               panels: [
                 {
      @@ -137,16 +137,16 @@ 

      4. Put it Together with LocusZoom.populate()

      } ] }; - var plot = LocusZoom.populate("#lz-plot", data_sources, layout); + const plot = LocusZoom.populate("#lz-plot", data_sources, layout); </script> </body> </html>

      Other Ways To Make a LocusZoom Plot

      Use a Predefined Layout

      -

      The core LocusZoom library comes equipped with several predefined layouts, organized by type ("plot", "panel", "data_layer", and "toolbar"). 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)).

      +

      The core LocusZoom library comes equipped with several predefined layouts, organized by type ("plot", "panel", "data_layer", and "toolbar"). You can see what layouts are predefined by reading the documentation or introspecting in the browser by entering LocusZoom.Layouts.list() (or to list one specific type, like "data_layer": LocusZoom.Layouts.list(type)).

      Get any predefined layout by type and name using LocusZoom.Layouts.get(type, name).

      -

      If your data matches the field names and formats of the UMich PortalDev API, these layouts will provide a quick way to get started. If your data obeys different format rules, customization may be necessary.

      +

      If your data matches the field names and formats of the UMich PortalDev API, these layouts will provide a quick way to get started. If your data obeys different format rules, customization may be necessary. (for example, some LocusZoom features assume the presence of a field called log_pvalue)

      See the guide to working with layouts for further details.

      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 @@ -167,8 +167,8 @@

      Build a Layout Using Some Predefined Pieces

    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:

    -
    const changes = { label_font_size: 20 };
    -LocusZoom.Layouts.get("data_layer", "genes", changes);
    +
    const overrides = { label_font_size: 20 };
    +LocusZoom.Layouts.get("data_layer", "genes", overrides);
     

    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:

    @@ -205,8 +205,8 @@

    Automated Testing

    Static analysis and code style

    LocusZoom runs code quality checks via ESLint, the rules for which can be found in .eslintrc. This will run automatically as part of all new code commits, and during every build.

    Help and Support

    -

    Full documentation can be found here: docs/

    -

    A LocusZoom discussion forum is available here: groups.google.com/forum/#!forum/locuszoom. +

    Full API documentation and prose guides are available at: https://statgen.github.io/locuszoom/docs/

    +

    A LocusZoom discussion forum is available here: https://groups.google.com/forum/#!forum/locuszoom. For the most effective help, please specify that your question is about "LocusZoom.js".

    If you have questions or feedback please file an issue on the LocusZoom.js GitHub repository or post at the discussion forum referenced above.

    diff --git a/docs/api/layouts_index.js.html b/docs/api/layouts_index.js.html index 44d93f24..f1fc8e0d 100644 --- a/docs/api/layouts_index.js.html +++ b/docs/api/layouts_index.js.html @@ -241,13 +241,15 @@

    Source: layouts/index.js

    '#AAAAAA', ], legend: [ - { shape: 'diamond', color: '#9632b8', size: 40, label: 'LD Ref Var', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(219, 61, 17)', size: 40, label: '1.0 > r² ≥ 0.8', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(248, 195, 42)', size: 40, label: '0.8 > r² ≥ 0.6', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(110, 254, 104)', size: 40, label: '0.6 > r² ≥ 0.4', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(38, 188, 225)', size: 40, label: '0.4 > r² ≥ 0.2', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: 'rgb(70, 54, 153)', size: 40, label: '0.2 > r² ≥ 0.0', class: 'lz-data_layer-scatter' }, - { shape: 'circle', color: '#AAAAAA', size: 40, label: 'no r² data', class: 'lz-data_layer-scatter' }, + { label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8' + { + shape: 'ribbon', + orientation: 'vertical', + width: 10, + height: 15, + color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'], + tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0], + }, ], label: null, z_index: 2, @@ -768,8 +770,8 @@

    Source: layouts/index.js

    id: 'association', tag: 'association', min_height: 200, - height: 225, - margin: { top: 35, right: 50, bottom: 40, left: 50 }, + height: 300, + margin: { top: 35, right: 55, bottom: 40, left: 70 }, inner_border: 'rgb(210, 210, 210)', toolbar: (function () { const base = deepCopy(standard_panel_toolbar); @@ -782,22 +784,22 @@

    Source: layouts/index.js

    axes: { x: { label: 'Chromosome {{chr}} (Mb)', - label_offset: 32, + label_offset: 38, tick_format: 'region', extent: 'state', }, y1: { label: '-log10 p-value', - label_offset: 28, + label_offset: 50, }, y2: { label: 'Recombination Rate (cM/Mb)', - label_offset: 40, + label_offset: 46, }, }, legend: { orientation: 'vertical', - origin: { x: 55, y: 40 }, + origin: { x: 75, y: 40 }, hidden: true, }, interaction: { @@ -824,19 +826,19 @@

    Source: layouts/index.js

    tag: 'coaccessibility', min_height: 150, height: 180, - margin: { top: 35, right: 50, bottom: 40, left: 50 }, + margin: { top: 35, right: 55, bottom: 40, left: 70 }, inner_border: 'rgb(210, 210, 210)', toolbar: deepCopy(standard_panel_toolbar), axes: { x: { label: 'Chromosome {{chr}} (Mb)', - label_offset: 32, + label_offset: 38, tick_format: 'region', extent: 'state', }, y1: { label: 'Score', - label_offset: 28, + label_offset: 40, render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter. }, }, @@ -896,7 +898,7 @@

    Source: layouts/index.js

    { field: 'ld:correlation', operator: '>', value: 0.4 }, ], style: { - 'font-size': '10px', + 'font-size': '12px', 'font-weight': 'bold', 'fill': '#333333', }, @@ -922,7 +924,7 @@

    Source: layouts/index.js

    tag: 'genes', min_height: 150, height: 225, - margin: { top: 20, right: 50, bottom: 20, left: 50 }, + margin: { top: 20, right: 55, bottom: 20, left: 70 }, axes: {}, interaction: { drag_background_to_pan: true, @@ -955,7 +957,7 @@

    Source: layouts/index.js

    tag: 'phewas', min_height: 300, height: 300, - margin: { top: 20, right: 50, bottom: 120, left: 50 }, + margin: { top: 20, right: 55, bottom: 120, left: 70 }, inner_border: 'rgb(210, 210, 210)', axes: { x: { @@ -971,7 +973,7 @@

    Source: layouts/index.js

    }, y1: { label: '-log10 p-value', - label_offset: 28, + label_offset: 50, }, }, data_layers: [ @@ -990,7 +992,7 @@

    Source: layouts/index.js

    tag: 'gwascatalog', min_height: 50, height: 50, - margin: { top: 25, right: 50, bottom: 10, left: 50 }, + margin: { top: 25, right: 55, bottom: 10, left: 70 }, inner_border: 'rgb(210, 210, 210)', toolbar: deepCopy(standard_panel_toolbar), axes: { @@ -1064,7 +1066,7 @@

    Source: layouts/index.js

    axes: { x: { label: 'Chromosome {{chr}} (Mb)', - label_offset: 32, + label_offset: 38, tick_format: 'region', extent: 'state', }, diff --git a/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html b/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html index e272e9b3..fe9c33f1 100644 --- a/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html +++ b/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html @@ -1585,7 +1585,7 @@
    Parameters:
    Source:
    @@ -1679,7 +1679,7 @@

    (protected, s
    Source:
    @@ -1753,7 +1753,7 @@
    Type:
    Source:
    @@ -1825,7 +1825,7 @@
    Type:
    Source:
    @@ -1900,7 +1900,7 @@
    Type:
    Source:
    @@ -1968,7 +1968,7 @@
    Type:
    Source:
    @@ -2036,7 +2036,7 @@
    Type:
    Source:
    @@ -2302,7 +2302,7 @@
    Parameters:
    Source:
    @@ -2454,7 +2454,7 @@
    Parameters:
    Source:
    @@ -2617,7 +2617,7 @@
    Parameters:
    Source:
    @@ -2729,7 +2729,7 @@

    (prote
    Source:
    @@ -2838,7 +2838,7 @@

    (protected)
    Source:
    @@ -3039,7 +3039,7 @@
    Parameters:
    Source:
    @@ -3198,7 +3198,7 @@
    Parameters:
    Source:
    @@ -3405,7 +3405,7 @@
    Parameters:
    Source:
    @@ -3520,7 +3520,7 @@

    moveBackSource:
    @@ -3626,7 +3626,7 @@

    moveForwar
    Source:
    @@ -3734,7 +3734,7 @@

    mutateLay
    Source:
    @@ -3818,7 +3818,7 @@

    renderSource:
    @@ -4008,7 +4008,7 @@
    Parameters:
    Source:
    @@ -4143,7 +4143,7 @@
    Parameters:
    Source:
    diff --git a/docs/api/module-LocusZoom_DataLayers.html b/docs/api/module-LocusZoom_DataLayers.html index 3e0afae1..9d8ce949 100644 --- a/docs/api/module-LocusZoom_DataLayers.html +++ b/docs/api/module-LocusZoom_DataLayers.html @@ -877,6 +877,8 @@
    Properties:
    + Default + Description @@ -905,10 +907,15 @@
    Properties:
    + + + +

    This is optional (e.g. a legend element could just be a textual label). Supported values are the standard d3 3.x symbol types (i.e. "circle", "cross", "diamond", "square", -"triangle-down", and "triangle-up"), as well as "rect" for an arbitrary square/rectangle or line for a path.

    +"triangle-down", and "triangle-up"), as well as "rect" for an arbitrary square/rectangle or "line" for a path. +A special "ribbon" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD)

    @@ -936,6 +943,10 @@
    Properties:
    + + + +

    The point color (hexadecimal, rgb, etc)

    @@ -965,6 +976,10 @@
    Properties:
    + + + +

    The human-readable label of the legend item

    @@ -996,6 +1011,10 @@
    Properties:
    + + + +

    The name of a CSS class used to style the point in the legend

    @@ -1027,6 +1046,10 @@
    Properties:
    + + + +

    The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area, a circle would be ~7..14 px in diameter.

    @@ -1059,6 +1082,10 @@
    Properties:
    + + + +

    Length (in pixels) for the path rendered as the graphical portion of the legend element if the value of the shape parameter is "line".

    @@ -1091,9 +1118,13 @@
    Properties:
    + + + +

    Width (in pixels) for the rect rendered as the graphical portion of the legend element if -the value of the shape parameter is "rect".

    +the value of the shape parameter is "rect" or "ribbon".

    @@ -1123,9 +1154,123 @@
    Properties:
    + + + +

    Height (in pixels) for the rect rendered as the graphical portion of the legend element if -the value of the shape parameter is "rect".

    +the value of the shape parameter is "rect" or "ribbon".

    + + + + + + + orientation + + + + + +'vertical' +| + +'horizontal' + + + + + + + + + <optional>
    + + + + + + + + + + 'vertical' + + + + +

    For shape "ribbon", specifies whether to draw the ribbon vertically or horizontally.

    + + + + + + + tick_labels + + + + + +Array + + + + + + + + + <optional>
    + + + + + + + + + + + + +

    For shape "ribbon", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops.

    + + + + + + + color_stops + + + + + +Array.<String> + + + + + + + + + <optional>
    + + + + + + + + + + + + +

    For shape "ribbon", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels.

    @@ -1153,6 +1298,10 @@
    Properties:
    + + + +

    CSS styles object to be applied to the DOM element representing the graphical portion of the legend element.

    diff --git a/docs/api/module-LocusZoom_Layouts.html b/docs/api/module-LocusZoom_Layouts.html index 05769baa..a880a2cd 100644 --- a/docs/api/module-LocusZoom_Layouts.html +++ b/docs/api/module-LocusZoom_Layouts.html @@ -200,7 +200,7 @@
    Type:
    Source:
    @@ -273,7 +273,7 @@
    Type:
    Source:
    @@ -583,7 +583,7 @@
    Type:
    Source:
    @@ -655,7 +655,7 @@
    Type:
    Source:
    @@ -727,7 +727,7 @@
    Type:
    Source:
    @@ -1187,7 +1187,7 @@
    Type:
    Source:
    @@ -1417,7 +1417,7 @@
    Type:
    Source:
    @@ -1489,7 +1489,7 @@
    Type:
    Source:
    @@ -1561,7 +1561,7 @@
    Type:
    Source:
    @@ -1634,7 +1634,7 @@
    Type:
    Source:
    @@ -1708,7 +1708,7 @@
    Type:
    Source:
    @@ -1780,7 +1780,7 @@
    Type:
    Source:
    @@ -1852,7 +1852,7 @@
    Type:
    Source:
    @@ -2485,7 +2485,7 @@
    Type:
    Source:
    @@ -2557,7 +2557,7 @@
    Type:
    Source:
    @@ -2629,7 +2629,7 @@
    Type:
    Source:
    @@ -2773,7 +2773,7 @@
    Type:
    Source:
    @@ -2917,7 +2917,7 @@
    Type:
    Source:
    @@ -2990,7 +2990,7 @@
    Type:
    Source:
    @@ -3141,7 +3141,7 @@
    Type:
    Source:
    @@ -3213,7 +3213,7 @@
    Type:
    Source:
    @@ -3285,7 +3285,7 @@
    Type:
    Source:
    diff --git a/docs/api/module-components_legend-Legend.html b/docs/api/module-components_legend-Legend.html index c7bc912f..74d9b827 100644 --- a/docs/api/module-components_legend-Legend.html +++ b/docs/api/module-components_legend-Legend.html @@ -813,7 +813,7 @@

    hideSource:
    @@ -901,7 +901,7 @@

    positionSource:
    @@ -1102,7 +1102,7 @@

    showSource:
    diff --git a/package-lock.json b/package-lock.json index 4d40b0ec..c46f969f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.14.0-beta.1", + "version": "0.14.0-beta.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 52661637..0174877d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.14.0-beta.1", + "version": "0.14.0-beta.2", "main": "dist/locuszoom.app.min.js", "module": "esm/index.js", "sideEffects": true, From a6dd80c501ad2a5c0d1875243cea01385d2ef614 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Fri, 19 Nov 2021 13:08:01 -0500 Subject: [PATCH 091/100] Add README reference to paper (and fix citation.cff) --- README.md | 5 +++++ citation.cff | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 41600cba..94ed192d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ LocusZoom is a Javascript/d3 embeddable plugin for interactively visualizing statistical genetic data from customizable sources. + +For more information, see our paper: + +*Boughton, A. P. et al. LocusZoom.js: interactive and embeddable visualization of genetic association study results. Bioinformatics (2021) [doi:10.1093/bioinformatics/btab186](https://doi.org/10.1093/bioinformatics/btab186).* + **This is a low level library aimed at developers who want to customize their own data sharing/visualization tools. If you are a genetics researcher who just wants to make a fast visualization of your research results, try our user-friendly plot-your-own data services built on LocusZoom.js: [my.locuszoom.org](https://my.locuszoom.org/) and [LocalZoom](https://statgen.github.io/localzoom/)**. diff --git a/citation.cff b/citation.cff index 7dc426d7..d21060a5 100644 --- a/citation.cff +++ b/citation.cff @@ -10,8 +10,8 @@ authors: family-names: Welch given-names: Ryan - - family-names: Matthew - given-names: Flickinger + family-names: Flickinger + given-names: Matthew - family-names: VandeHaar given-names: Peter From 33316cde4b0270aa7b8c027a68b8f65b1bd843f5 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 13 Dec 2021 17:09:08 -0500 Subject: [PATCH 092/100] Fix regression where sometimes ldrefvar would be marked incorrectly (due to usages where chrom was specified as int instead of string) --- esm/data/adapters.js | 2 +- examples/coaccessibility.html | 2 +- examples/ext/credible_sets.html | 2 +- examples/ext/tabix_tracks.html | 2 +- examples/gwas_catalog.html | 2 +- index.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 214d40b8..26fb78ec 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -519,7 +519,7 @@ class LDServer extends BaseUMAdapter { const coord = +pos; // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby. - if ((coord && state.ldrefvar && state.chr) && (chrom !== state.chr || coord < state.start || coord > state.end)) { + if ((coord && state.ldrefvar && state.chr) && (chrom !== String(state.chr) || coord < state.start || coord > state.end)) { // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a // *copy* of plot.state, so wiping here doesn't remove the original value. state.ldrefvar = null; diff --git a/examples/coaccessibility.html b/examples/coaccessibility.html index aa5014c9..14d01abf 100644 --- a/examples/coaccessibility.html +++ b/examples/coaccessibility.html @@ -94,7 +94,7 @@
    < return home
    // Fetch initial position from the URL, or use some defaults var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { - initialState = { chr: 10, start: 114176406, end: 115176406 }; + initialState = { chr: '10', start: 114176406, end: 115176406 }; } layout = LocusZoom.Layouts.get("plot", "coaccessibility", { state: initialState }); LocusZoom.Layouts.mutate_attrs(layout, '$.panels[?(@.tag === "coaccessibility")]', (config) => { diff --git a/examples/ext/credible_sets.html b/examples/ext/credible_sets.html index 12928e60..744c79b5 100644 --- a/examples/ext/credible_sets.html +++ b/examples/ext/credible_sets.html @@ -104,7 +104,7 @@

    Top Hits

    // Fetch initial position from the URL, or use some defaults var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { - initialState = {chr: 16, start: 74947245, end: 75547245}; + initialState = {chr: '16', start: 74947245, end: 75547245}; } layout = LocusZoom.Layouts.get("plot", "association_credible_set", {state: initialState}); diff --git a/examples/ext/tabix_tracks.html b/examples/ext/tabix_tracks.html index 2bff7c74..377e0c5f 100644 --- a/examples/ext/tabix_tracks.html +++ b/examples/ext/tabix_tracks.html @@ -218,7 +218,7 @@ // Fetch initial position from the URL, or use some defaults var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { - initialState = { chr: 16, start: 53609247, end: 54009247 }; + initialState = { chr: '16', start: 53609247, end: 54009247 }; } const layout = LocusZoom.Layouts.get("plot", "standard_association", { state: initialState, diff --git a/examples/gwas_catalog.html b/examples/gwas_catalog.html index 9762c469..84df8199 100644 --- a/examples/gwas_catalog.html +++ b/examples/gwas_catalog.html @@ -100,7 +100,7 @@

    Top Hits

    // Fetch initial position from the URL, or use some defaults var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { - initialState = { chr: 9, start: 21751670, end: 22351670 }; + initialState = { chr: '9', start: 21751670, end: 22351670 }; } // This example demonstrates using the `state.genome_build` param, which tells LZ annotation sources to // automatically select an appropriate dataset based on a globally specified genome build. diff --git a/index.html b/index.html index 39eb2547..b722e3fc 100644 --- a/index.html +++ b/index.html @@ -229,7 +229,7 @@
    Tabix tracks
    // Fetch initial position from the URL, or use some defaults var initialState = LzDynamicUrls.paramsFromUrl(stateUrlMapping); if (!Object.keys(initialState).length) { - initialState = {chr: 10, start: 114550452, end: 115067678}; + initialState = {chr: '10', start: 114550452, end: 115067678}; } layout = LocusZoom.Layouts.get("plot", "standard_association", {state: initialState}); From a5587ef0146b95fc1277f355a58ac90c2acabd7a Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 27 Jan 2022 21:09:48 -0500 Subject: [PATCH 093/100] Inline undercomplicate (data retrieval library) and improve adapter documentation in jsdoc for existing adapters / methods --- .eslintrc.js | 2 +- esm/components/data_layer/base.js | 4 +- esm/components/data_layer/genes.js | 2 +- esm/components/plot.js | 2 +- esm/data/adapters.js | 44 ++-- esm/data/requester.js | 2 +- esm/data/undercomplicate/adapter.js | 205 ++++++++++++++++++ esm/data/undercomplicate/index.js | 17 ++ esm/data/undercomplicate/joins.js | 109 ++++++++++ esm/data/undercomplicate/lru_cache.js | 163 ++++++++++++++ esm/data/undercomplicate/requests.js | 99 +++++++++ esm/data/undercomplicate/util.js | 19 ++ esm/ext/lz-credible-sets.js | 4 +- esm/ext/lz-parsers/gwas/parsers.js | 2 +- esm/ext/lz-parsers/gwas/sniffers.js | 2 +- esm/helpers/common.js | 2 +- esm/helpers/layouts.js | 4 +- esm/layouts/index.js | 6 +- esm/registry/data_ops.js | 2 +- esm/registry/transforms.js | 2 +- package-lock.json | 193 ++++++++++------- package.json | 6 +- .../data_layer/test_highlight_regions.js | 4 +- test/unit/components/test_datalayer.js | 6 +- test/unit/components/test_panel.js | 2 +- test/unit/components/test_plot.js | 14 +- test/unit/components/test_widgets.js | 2 +- test/unit/data/test_adapters.js | 20 +- test/unit/data/test_data_ops.js | 8 +- test/unit/data/test_requester.js | 18 +- .../data/undercomplicate/test_adapters.js | 170 +++++++++++++++ test/unit/data/undercomplicate/test_cache.js | 83 +++++++ test/unit/data/undercomplicate/test_joins.js | 57 +++++ .../data/undercomplicate/test_requests.js | 79 +++++++ .../unit/ext/lz-parsers/gwas/test_sniffers.js | 18 +- test/unit/ext/test_dynamic-urls.js | 4 +- test/unit/ext/test_ext_intervals-track.js | 8 +- test/unit/ext/test_tabix-source.js | 4 +- test/unit/helpers/test_jsonpath.js | 4 +- test/unit/helpers/test_layouts.js | 4 +- test/unit/helpers/test_render.js | 10 +- test/unit/test_layouts.js | 4 +- test/unit/test_transforms.js | 2 +- 43 files changed, 1228 insertions(+), 184 deletions(-) create mode 100644 esm/data/undercomplicate/adapter.js create mode 100644 esm/data/undercomplicate/index.js create mode 100644 esm/data/undercomplicate/joins.js create mode 100644 esm/data/undercomplicate/lru_cache.js create mode 100644 esm/data/undercomplicate/requests.js create mode 100644 esm/data/undercomplicate/util.js create mode 100644 test/unit/data/undercomplicate/test_adapters.js create mode 100644 test/unit/data/undercomplicate/test_cache.js create mode 100644 test/unit/data/undercomplicate/test_joins.js create mode 100644 test/unit/data/undercomplicate/test_requests.js diff --git a/.eslintrc.js b/.eslintrc.js index 2bc22599..37ac2752 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -45,7 +45,7 @@ module.exports = { 'node': true, }, 'parserOptions': { - 'ecmaVersion': 2016, + 'ecmaVersion': 2020, 'sourceType': 'module', }, }; diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index 1c7536fd..15d3f334 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -1349,7 +1349,7 @@ class BaseDataLayer { // The broadcast value can use transforms to "clean up value before sending broadcasting" 'match_requested', { value: new Field(value_to_broadcast).resolve(element), active: active }, - true + true, ); } return this; @@ -1572,7 +1572,7 @@ class BaseDataLayer { this.parent.emit( 'data_from_layer', { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin - true + true, ); }); } diff --git a/esm/components/data_layer/genes.js b/esm/components/data_layer/genes.js index be4e44c5..14e07f94 100644 --- a/esm/components/data_layer/genes.js +++ b/esm/components/data_layer/genes.js @@ -310,7 +310,7 @@ class Genes extends BaseDataLayer { }) .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()) + data_layer.layout.bounding_box_padding - + data_layer.layout.label_font_size + + data_layer.layout.label_font_size, ); labels.exit() diff --git a/esm/components/plot.js b/esm/components/plot.js index bfc7de02..9a7eb5aa 100644 --- a/esm/components/plot.js +++ b/esm/components/plot.js @@ -1097,7 +1097,7 @@ class Plot { const panel = this.panels[panel_id]; panel.setDimensions( this.layout.width, - panel.layout.height + panel.layout.height, ); }); diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 26fb78ec..7f964908 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -32,15 +32,10 @@ * @module LocusZoom_Adapters */ -import {BaseUrlAdapter} from 'undercomplicate'; +import {BaseUrlAdapter} from './undercomplicate'; import {parseMarker} from '../helpers/parse'; -// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs. -// Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal -// methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the -// private API methods exist in the base class. - /** * Replaced with the BaseLZAdapter class. * @public @@ -64,17 +59,20 @@ class BaseApiAdapter extends BaseAdapter {} /** - * @param {object} config - * @param [config.cache_enabled=true] - * @param [config.cache_size=3] - * @param [config.url] - * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name. - * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant) - * Typically, this is only disabled if the response payload is very unusual - * @param {String[]} [limit_fields=null] If an API returns far more data than is needed, this can be used to simplify - * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD. + * @extends module:undercomplicate.BaseUrlAdapter + * @inheritDoc */ class BaseLZAdapter extends BaseUrlAdapter { + /** + * @param [config.cache_enabled=true] + * @param [config.cache_size=3] + * @param [config.url] + * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name. + * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant) + * Typically, this is only disabled if the response payload is very unusual + * @param {String[]} [config.limit_fields=null] If an API returns far more data than is needed, this can be used to simplify + * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD. + */ constructor(config = {}) { if (config.params) { // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places. @@ -96,10 +94,11 @@ class BaseLZAdapter extends BaseUrlAdapter { /** * Determine how a particular request will be identified in cache. Most LZ requests are region based, - * so the default is a string concatenation of `chr_start_end` + * so the default is a string concatenation of `chr_start_end`. This adapter is "region aware"- if the user + * zooms in, it won't trigger a network request because we alread have the data needed. * @param options Receives plot.state plus any other request options defined by this source * @returns {string} - * @private + * @public */ _getCacheKey(options) { // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default @@ -125,7 +124,7 @@ class BaseLZAdapter extends BaseUrlAdapter { * @param records * @param options * @returns {*} - * @private + * @public */ _postProcessResponse(records, options) { if (!this._prefix_namespace || !Array.isArray(records)) { @@ -146,7 +145,7 @@ class BaseLZAdapter extends BaseUrlAdapter { } return acc; }, - {} + {}, ); }); } @@ -177,11 +176,13 @@ class BaseLZAdapter extends BaseUrlAdapter { /** * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies * of one particular web server. + * @extends module:LocusZoom_Adapters~BaseLZAdapter + * @inheritDoc */ class BaseUMAdapter extends BaseLZAdapter { /** * @param {Object} config - * @param {String} [config.build] The genome build to be used by all requests for this adapter. + * @param {String} [config.build] The genome build to be used by all requests for this adapter. (UMich APIs are all genome build aware). "GRCh37" or "GRCh38" */ constructor(config = {}) { super(config); @@ -206,7 +207,7 @@ class BaseUMAdapter extends BaseLZAdapter { * @param response_text * @param options * @returns {Object[]} - * @private + * @public */ _normalizeResponse(response_text, options) { let data = super._normalizeResponse(...arguments); @@ -249,6 +250,7 @@ class BaseUMAdapter extends BaseLZAdapter { * to a specific REST API. * @public * @see module:LocusZoom_Adapters~BaseUMAdapter + * @inheritDoc * * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL */ diff --git a/esm/data/requester.js b/esm/data/requester.js index 6a0b2b91..ce842bd6 100644 --- a/esm/data/requester.js +++ b/esm/data/requester.js @@ -2,7 +2,7 @@ * @module * @private */ -import {getLinkedData} from 'undercomplicate'; +import {getLinkedData} from './undercomplicate'; import { DATA_OPS } from '../registry'; diff --git a/esm/data/undercomplicate/adapter.js b/esm/data/undercomplicate/adapter.js new file mode 100644 index 00000000..4debe5fd --- /dev/null +++ b/esm/data/undercomplicate/adapter.js @@ -0,0 +1,205 @@ +import {LRUCache} from './lru_cache'; +import {clone} from './util'; + +/** + * @param {boolean} [config.cache_enabled=true] Whether to enable the LRU cache, and store a copy of the normalized and parsed response data. + * Turned on by default for most remote requests; turn off if you are using another datastore (like Vuex) or if the response body uses too much memory. + * @param {number} [config.cache_size=3] How many requests to cache. Track-dependent annotations like LD might benefit + * from caching more items, while very large payloads (like, um, TOPMED LD) might benefit from a smaller cache size. + * For most LocusZoom usages, the cache is "region aware": zooming in will use cached data, not a separate request + * @inheritDoc + * @memberOf module:undercomplicate + */ +class BaseAdapter { + constructor(config = {}) { + this._config = config; + const { + // Cache control + cache_enabled = true, + cache_size = 3, + } = config; + this._enable_cache = cache_enabled; + this._cache = new LRUCache(cache_size); + } + + /** + * Build an object with options that control the request. This can take into account both explicit options, and prior data. + * @param {Object} options Any global options passed in via `getData`. Eg, in locuszoom, every request is passed a copy of `plot.state` as the options object, in which case every adapter would expect certain basic information like `chr, start, end` to be available. + * @param {Object[]} dependent_data If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, `ld(assoc)` means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments. + * @returns {*} An options object containing initial options, plus any calculated values relevant to the request. + * @public + */ + _buildRequestOptions(options, dependent_data) { + // Perform any pre-processing required that may influence the request. Receives an array with the payloads + // for each request that preceded this one in the dependency chain + // This method may optionally take dependent data into account. For many simple adapters, there won't be any dependent data! + return Object.assign({}, options); + } + + /** + * Determine how this request is uniquely identified in cache. Usually this is an exact match for the same key, but it doesn't have to be. + * The LRU cache implements a `find` method, which means that a cache item can optionally be identified by its node + * `metadata` (instead of exact key match). + * This is useful for situations where the user zooms in to a smaller region and wants the original request to + * count as a cache hit. See subclasses for example. + * @param {object} options Request options from `_buildRequestOptions` + * @returns {*} This is often a string concatenating unique values for a compound cache key, like `chr_start_end`. If null, it is treated as a cache miss. + * @public + */ + _getCacheKey(options) { + /* istanbul ignore next */ + if (this._enable_cache) { + throw new Error('Method not implemented'); + } + return null; + } + + /** + * Perform the act of data retrieval (eg from a URL, blob, or JSON entity) + * @param {object} options Request options from `_buildRequestOptions` + * @returns {Promise} + * @public + */ + _performRequest(options) { + /* istanbul ignore next */ + throw new Error('Not implemented'); + } + + /** + * Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json. + * @param {*} response_text The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON. + * @param {Object} options Request options. These are not typically used when normalizing a response, but the object is available. + * @returns {*} A list of objects, each object representing one row of data `{column_name: value_for_row}` + * @public + */ + _normalizeResponse(response_text, options) { + return response_text; + } + + /** + * Perform custom client-side operations on the retrieved data. For example, add calculated fields or + * perform rapid client-side filtering on cached data. Annotations are applied after cache, which means + * that the same network request can be dynamically annotated/filtered in different ways in response to user interactions. + * + * This result is currently not cached, but it may become so in the future as responsibility for dynamic UI + * behavior moves to other layers of the application. + * @param {Object[]} records + * @param {Object} options + * @returns {*} + * @public + */ + _annotateRecords(records, options) { + return records; + } + + /** + * A hook to transform the response after all operations are done. For example, this can be used to prefix fields + * with a namespace unique to the request, like `log_pvalue` -> `assoc:log_pvalue`. (by applying namespace prefixes to field names last, + * annotations and validation can happen on the actual API payload, without having to guess what the fields were renamed to). + * @param records + * @param options + * @public + */ + _postProcessResponse(records, options) { + return records; + } + + /** + * All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data. + * @param {object} options Shared options for this request. In LocusZoom, this is typically a copy of `plot.state`. + * @param {Array[]} dependent_data Zero or more recordsets corresponding to each individual adapter that this one depends on. + * Can be used to build a request that takes into account prior data. + * @returns {Promise<*>} + */ + getData(options = {}, ...dependent_data) { + // Public facing method to define, perform, and process the request + options = this._buildRequestOptions(options, ...dependent_data); + + const cache_key = this._getCacheKey(options); + + // Then retrieval and parse steps: parse + normalize response, annotate + let result; + if (this._enable_cache && this._cache.has(cache_key)) { + result = this._cache.get(cache_key); + } else { + // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`) + // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry + // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry, + // even if the actual cache key wasn't an exact match. (see subclasses for an example; this class is generic) + result = Promise.resolve(this._performRequest(options)) + // Note: we cache the normalized (parsed) response + .then((text) => this._normalizeResponse(text, options)); + this._cache.add(cache_key, result, options._cache_meta); + // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request + // Otherwise, temporary failures couldn't be resolved by trying again in a moment + // TODO: In the future, consider providing a way to skip requests (eg, a sentinel value to flag something + // as not cacheable, like "no dependent data means no request... but maybe in another place this is used, there will be different dependent data and a request would make sense") + result.catch((e) => this._cache.remove(cache_key)); + } + + return result + // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache + .then((data) => clone(data)) + .then((records) => this._annotateRecords(records, options)) + .then((records) => this._postProcessResponse(records, options)); + } +} + + +/** + * Fetch data over the web, usually from a REST API that returns JSON + * @param {string} config.url The URL to request + * @extends module:undercomplicate.BaseAdapter + * @inheritDoc + * @memberOf module:undercomplicate + */ +class BaseUrlAdapter extends BaseAdapter { + constructor(config = {}) { + super(config); + this._url = config.url; + } + + + /** + * Default cache key is the URL for this request + * @public + */ + _getCacheKey(options) { + return this._getURL(options); + } + + /** + * In many cases, the base url should be modified with query parameters based on request options. + * @param options + * @returns {*} + * @private + */ + _getURL(options) { + return this._url; + } + + _performRequest(options) { + const url = this._getURL(options); + // Many resources will modify the URL to add query or segment parameters. Base method provides option validation. + // (not validating in constructor allows URL adapter to be used as more generic parent class) + if (!this._url) { + throw new Error('Web based resources must specify a resource URL as option "url"'); + } + return fetch(url).then((response) => { + if (!response.ok) { + throw new Error(response.statusText); + } + return response.text(); + }); + } + + _normalizeResponse(response_text, options) { + if (typeof response_text === 'string') { + return JSON.parse(response_text); + } + // Some custom usages will return other datatypes. These would need to be handled by custom normalization logic in a subclass. + return response_text; + } +} + +export { BaseAdapter, BaseUrlAdapter }; diff --git a/esm/data/undercomplicate/index.js b/esm/data/undercomplicate/index.js new file mode 100644 index 00000000..0c1fcae2 --- /dev/null +++ b/esm/data/undercomplicate/index.js @@ -0,0 +1,17 @@ +/** + * The LocusZoom data retrieval library was originally created as a standalone library, mainly for LZ usage. + * It is inlined into the LocusZoom source code, because there are no other places it is really used, and JSDoc references are much easier + * to generate with a package in the same repo. + * + * See individual adapter classes (and their documentation) for helpful guides on what methods are available, and common customizations for LocusZoom use. + * @see module:LocusZoom_Adapters~BaseLZAdapter + * + * @module undercomplicate + * @public + */ +export { BaseAdapter, BaseUrlAdapter } from './adapter'; +export {LRUCache} from './lru_cache'; +export {getLinkedData} from './requests'; + +import * as joins from './joins'; +export {joins}; diff --git a/esm/data/undercomplicate/joins.js b/esm/data/undercomplicate/joins.js new file mode 100644 index 00000000..95475e60 --- /dev/null +++ b/esm/data/undercomplicate/joins.js @@ -0,0 +1,109 @@ +/** + * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key. + */ +import { clone } from './util'; + + +/** + * Simple grouping function, used to identify sets of records for joining. + * + * Used internally by join helpers, exported mainly for unit testing + * @memberOf module:undercomplicate + * @param {object[]} records + * @param {string} group_key + * @returns {Map} + */ +function groupBy(records, group_key) { + const result = new Map(); + for (let item of records) { + const item_group = item[group_key]; + + if (typeof item_group === 'undefined') { + throw new Error(`All records must specify a value for the field "${group_key}"`); + } + if (typeof item_group === 'object') { + // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys) + throw new Error('Attempted to group on a field with non-primitive values'); + } + + let group = result.get(item_group); + if (!group) { + group = []; + result.set(item_group, group); + } + group.push(item); + } + return result; +} + + +function _any_match(type, left, right, left_key, right_key) { + // Helper that consolidates logic for all three join types + const right_index = groupBy(right, right_key); + const results = []; + for (let item of left) { + const left_match_value = item[left_key]; + const right_matches = right_index.get(left_match_value) || []; + if (right_matches.length) { + // Record appears on both left and right; equiv to an inner join + results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item)))); + } else if (type !== 'inner') { + // Record appears on left but not right + results.push(clone(item)); + } + } + + if (type === 'outer') { + // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side + const left_index = groupBy(left, left_key); + for (let item of right) { + const right_match_value = item[right_key]; + const left_matches = left_index.get(right_match_value) || []; + if (!left_matches.length) { + results.push(clone(item)); + } + } + } + return results; +} + +/** + * Equivalent to LEFT OUTER JOIN in SQL. Return all left records, joined to matching right data where appropriate. + * @memberOf module:undercomplicate + * @param {Object[]} left The left side recordset + * @param {Object[]} right The right side recordset + * @param {string} left_key The join field in left records + * @param {string} right_key The join field in right records + * @returns {Object[]} + */ +function left_match(left, right, left_key, right_key) { + return _any_match('left', ...arguments); +} + +/** + * Equivalent to INNER JOIN in SQL. Only return record joins if the key field has a match on both left and right. + * @memberOf module:undercomplicate + * @param {object[]} left The left side recordset + * @param {object[]} right The right side recordset + * @param {string} left_key The join field in left records + * @param {string} right_key The join field in right records + * @returns {Object[]} + */ +function inner_match(left, right, left_key, right_key) { + return _any_match('inner', ...arguments); +} + +/** + * Equivalent to FULL OUTER JOIN in SQL. Return records in either recordset, joined where appropriate. + * @memberOf module:undercomplicate + * @param {object[]} left The left side recordset + * @param {object[]} right The right side recordset + * @param {string} left_key The join field in left records + * @param {string} right_key The join field in right records + * @returns {Object[]} + */ +function full_outer_match(left, right, left_key, right_key) { + return _any_match('outer', ...arguments); +} + +export {left_match, inner_match, full_outer_match, groupBy}; diff --git a/esm/data/undercomplicate/lru_cache.js b/esm/data/undercomplicate/lru_cache.js new file mode 100644 index 00000000..5c16cab2 --- /dev/null +++ b/esm/data/undercomplicate/lru_cache.js @@ -0,0 +1,163 @@ +/** + * Implement an LRU Cache + */ + + +class LLNode { + /** + * A single node in the linked list. Users will only need to deal with this class if using "approximate match" (`cache.find()`) + * @memberOf module:undercomplicate + * @param {string} key + * @param {*} value + * @param {object} metadata + * @param {LLNode} prev + * @param {LLNode} next + */ + constructor(key, value, metadata = {}, prev = null, next = null) { + this.key = key; + this.value = value; + this.metadata = metadata; + this.prev = prev; + this.next = next; + } +} + +class LRUCache { + /** + * Least-recently used cache implementation, with "approximate match" semantics + * @memberOf module:undercomplicate + * @param {number} [max_size=3] + */ + constructor(max_size = 3) { + this._max_size = max_size; + this._cur_size = 0; // replace with map.size so we aren't managing manually? + this._store = new Map(); + + // Track LRU state + this._head = null; + this._tail = null; + + // Validate options + if (max_size === null || max_size < 0) { + throw new Error('Cache "max_size" must be >= 0'); + } + } + + /** + * Check key membership without updating LRU + * @param key + * @returns {boolean} + */ + has(key) { + return this._store.has(key); + } + + /** + * Retrieve value from cache (if present) and update LRU cache to say an item was recently used + * @param key + * @returns {null|*} + */ + get(key) { + const cached = this._store.get(key); + if (!cached) { + return null; + } + if (this._head !== cached) { + // Rewrite the cached value to ensure it is head of the list + this.add(key, cached.value); + } + return cached.value; + } + + /** + * Add an item. Forcibly replaces the existing cached value for the same key. + * @param key + * @param value + * @param {Object} [metadata={}) Metadata associated with an item. Metadata can be used for lookups (`cache.find`) to test for a cache hit based on non-exact match + */ + add(key, value, metadata = {}) { + if (this._max_size === 0) { + // Don't cache items if cache has 0 size. + return; + } + + const prior = this._store.get(key); + if (prior) { + this._remove(prior); + } + + const node = new LLNode(key, value, metadata, null, this._head); + + if (this._head) { + this._head.prev = node; + } else { + this._tail = node; + } + + this._head = node; + this._store.set(key, node); + + if (this._max_size >= 0 && this._cur_size >= this._max_size) { + const old = this._tail; + this._tail = this._tail.prev; + this._remove(old); + } + this._cur_size += 1; + } + + + // Cache manipulation methods + clear() { + this._head = null; + this._tail = null; + this._cur_size = 0; + this._store = new Map(); + } + + // Public method, remove by key + remove(key) { + const cached = this._store.get(key); + if (!cached) { + return false; + } + this._remove(cached); + return true; + } + + // Internal implementation, useful when working on list + _remove(node) { + if (node.prev !== null) { + node.prev.next = node.next; + } else { + this._head = node.next; + } + + if (node.next !== null) { + node.next.prev = node.prev; + } else { + this._tail = node.prev; + } + this._store.delete(node.key); + this._cur_size -= 1; + } + + /** + * Find a matching item in the cache, or return null. This is useful for "approximate match" semantics, + * to check if something qualifies as a cache hit despite not having the exact same key. + * (Example: zooming into a region, where the smaller region is a subset of something already cached) + * @param {function} match_callback A function to be used to test the node as a possible match (returns true or false) + * @returns {null|LLNode} + */ + find(match_callback) { + let node = this._head; + while (node) { + const next = node.next; + if (match_callback(node)) { + return node; + } + node = next; + } + } +} + +export { LRUCache }; diff --git a/esm/data/undercomplicate/requests.js b/esm/data/undercomplicate/requests.js new file mode 100644 index 00000000..aaca7424 --- /dev/null +++ b/esm/data/undercomplicate/requests.js @@ -0,0 +1,99 @@ +/** + * Perform a series of requests, respecting order of operations + * @private + */ + +import {Sorter} from '@hapi/topo'; + + +function _parse_declaration(spec) { + // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph. + const parsed = /^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(spec); + if (!parsed) { + throw new Error(`Unable to parse dependency specification: ${spec}`); + } + + let {name_alone, name_deps, deps} = parsed.groups; + if (name_alone) { + return [name_alone, []]; + } + + deps = deps.split(/\s*,\s*/); + return [name_deps, deps]; +} + +/** + * Perform a request for data from a set of providers, taking into account dependencies between requests. + * This can be a mix of network requests or other actions (like join tasks), provided that the provider be some + * object that implements a method `instance.getData` + * + * Each data layer in LocusZoom will translate the internal configuration into a format used by this function. + * This function is never called directly in custom user code. In locuszoom, Requester Handles You + * + * TODO Future: It would be great to add a warning if the final element in the DAG does not reference all dependencies. This is a limitation of the current toposort library we use. + * + * @param {object} shared_options Options passed globally to all requests. In LocusZoom, this is often a copy of "plot.state" + * @param {Map} entities A lookup of named entities that implement the method `instance.getData -> Promise` + * @param {String[]} dependencies A description of how to fetch entities, and what they depend on, like `['assoc', 'ld(assoc)']`. + * **Order will be determined by a DAG and the last item in the DAG is all that is returned.** + * @param {boolean} [consolidate=true] Whether to return all results (almost never used), or just the last item in the resolved DAG. + * This can be a pitfall in common usage: if you forget a "join/consolidate" task, the last result may appear to be missing some data. + * @returns {Promise} + */ +function getLinkedData(shared_options, entities, dependencies, consolidate = true) { + if (!dependencies.length) { + return []; + } + + const parsed = dependencies.map((spec) => _parse_declaration(spec)); + const dag = new Map(parsed); + + // Define the order to perform requests in, based on a DAG + const toposort = new Sorter(); + for (let [name, deps] of dag.entries()) { + try { + toposort.add(name, {after: deps, group: name}); + } catch (e) { + throw new Error(`Invalid or possible circular dependency specification for: ${name}`); + } + } + const order = toposort.nodes; + + // Verify that all requested entities exist by name! + const responses = new Map(); + for (let name of order) { + const provider = entities.get(name); + if (!provider) { + throw new Error(`Data has been requested from source '${name}', but no matching source was provided`); + } + + // Each promise should only be triggered when the things it depends on have been resolved + const depends_on = dag.get(name) || []; + const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name))); + + const this_result = prereq_promises.then((prior_results) => { + // Each request will be told the name of the provider that requested it. This can be used during post-processing, + // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id) + // This has a secondary effect: it ensures that any changes made to "shared" options in one adapter will + // not leak out to others via a mutable shared object reference. + const options = Object.assign({_provider_name: name}, shared_options); + return provider.getData(options, ...prior_results); + }); + responses.set(name, this_result); + } + return Promise.all([...responses.values()]) + .then((all_results) => { + if (consolidate) { + // Some usages- eg fetch + data join tasks- will only require the last response in the sequence + // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified) + return all_results[all_results.length - 1]; + } + return all_results; + }); +} + + +export {getLinkedData}; + +// For testing only +export {_parse_declaration}; diff --git a/esm/data/undercomplicate/util.js b/esm/data/undercomplicate/util.js new file mode 100644 index 00000000..42e65468 --- /dev/null +++ b/esm/data/undercomplicate/util.js @@ -0,0 +1,19 @@ +/** + * @private + */ + +import justclone from 'just-clone'; + +/** + * The "just-clone" library only really works for objects and arrays. If given a string, it would mess things up quite a lot. + * @param {object} data + * @returns {*} + */ +function clone(data) { + if (typeof data !== 'object') { + return data; + } + return justclone(data); +} + +export { clone }; diff --git a/esm/ext/lz-credible-sets.js b/esm/ext/lz-credible-sets.js index 686cc1e1..5e4bcbef 100644 --- a/esm/ext/lz-credible-sets.js +++ b/esm/ext/lz-credible-sets.js @@ -56,7 +56,7 @@ function install (LocusZoom) { // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p) this._config = Object.assign( { threshold: 0.95, significance_threshold: 7.301 }, - this._config + this._config, ); this._prefix_namespace = false; } @@ -382,7 +382,7 @@ function install (LocusZoom) { }, }, ], - } + }, ); return l; }(); diff --git a/esm/ext/lz-parsers/gwas/parsers.js b/esm/ext/lz-parsers/gwas/parsers.js index 006d8366..c9f54bef 100644 --- a/esm/ext/lz-parsers/gwas/parsers.js +++ b/esm/ext/lz-parsers/gwas/parsers.js @@ -52,7 +52,7 @@ function makeGWASParser( n_samples_col, is_alt_effect = true, // whether effect allele is oriented towards alt. We don't support files like METAL, where ref/alt may switch places per line of the file delimiter = '\t', - } + }, ) { // Column IDs should be 1-indexed (human friendly) if (has(marker_col) && has(chrom_col) && has(pos_col)) { diff --git a/esm/ext/lz-parsers/gwas/sniffers.js b/esm/ext/lz-parsers/gwas/sniffers.js index c07da291..e89562a2 100644 --- a/esm/ext/lz-parsers/gwas/sniffers.js +++ b/esm/ext/lz-parsers/gwas/sniffers.js @@ -55,7 +55,7 @@ function levenshtein(a, b) { // https://github.com/trekhleb/javascript-algorithm distanceMatrix[j][i] = Math.min( distanceMatrix[j][i - 1] + 1, // deletion distanceMatrix[j - 1][i] + 1, // insertion - distanceMatrix[j - 1][i - 1] + indicator // substitution + distanceMatrix[j - 1][i - 1] + indicator, // substitution ); } } diff --git a/esm/helpers/common.js b/esm/helpers/common.js index c80abf50..e0127e06 100644 --- a/esm/helpers/common.js +++ b/esm/helpers/common.js @@ -247,7 +247,7 @@ function debounce(func, delay = 500) { clearTimeout(timer); timer = setTimeout( () => func.apply(this, arguments), - delay + delay, ); }; } diff --git a/esm/helpers/layouts.js b/esm/helpers/layouts.js index e9fc4b54..e61a1380 100644 --- a/esm/helpers/layouts.js +++ b/esm/helpers/layouts.js @@ -200,7 +200,7 @@ function renameField(layout, old_name, new_name, warn_transforms = true) { (acc, key) => { acc[key] = renameField(layout[key], old_name, new_name, warn_transforms); return acc; - }, {} + }, {}, ); } else if (this_type !== 'string') { // Field names are always strings. If the value isn't a string, don't even try to change it. @@ -240,7 +240,7 @@ function mutate_attrs(layout, selector, value_or_callable) { return mutate( layout, selector, - value_or_callable + value_or_callable, ); } diff --git a/esm/layouts/index.js b/esm/layouts/index.js index d1b5e633..a9e8f0ef 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -722,7 +722,7 @@ const region_nav_plot_toolbar = function () { button_html: '<<', position: 'right', group_position: 'start', - } + }, ); return base; }(); @@ -911,7 +911,7 @@ const genes_panel = { position: 'right', button_html: 'Resize', }, - deepCopy(gene_selector_menu) + deepCopy(gene_selector_menu), ); return base; })(), @@ -1067,7 +1067,7 @@ const coaccessibility_plot = { // This is a companion to the "match" directive in the coaccessibility panel const base = Object.assign( { height: 270 }, - deepCopy(genes_panel) + deepCopy(genes_panel), ); const layer = base.data_layers[0]; layer.match = { send: 'gene_name', receive: 'gene_name' }; diff --git a/esm/registry/data_ops.js b/esm/registry/data_ops.js index 630caf13..a4faf0b8 100644 --- a/esm/registry/data_ops.js +++ b/esm/registry/data_ops.js @@ -26,7 +26,7 @@ * * @module LocusZoom_DataFunctions */ -import {joins} from 'undercomplicate'; +import {joins} from '../data/undercomplicate'; import {RegistryBase} from './base'; diff --git a/esm/registry/transforms.js b/esm/registry/transforms.js index d88b33f5..91635601 100644 --- a/esm/registry/transforms.js +++ b/esm/registry/transforms.js @@ -23,7 +23,7 @@ class TransformationFunctionsRegistry extends RegistryBase { return (value) => { return funcs.reduce( (acc, func) => func(acc), - value + value, ); }; } diff --git a/package-lock.json b/package-lock.json index c46f969f..f49e8a5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1104,19 +1104,18 @@ "dev": true }, "@eslint/eslintrc": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", - "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", - "globals": "^12.1.0", + "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, @@ -1134,13 +1133,19 @@ } }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, @@ -1157,6 +1162,23 @@ "@hapi/hoek": "^9.0.0" } }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2915,29 +2937,32 @@ } }, "eslint": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz", - "integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.3.0", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", "espree": "^7.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^6.0.0", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -2945,7 +2970,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.20", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -2954,7 +2979,7 @@ "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", - "table": "^6.0.4", + "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -2969,9 +2994,9 @@ } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -2984,9 +3009,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -3008,13 +3033,19 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" } }, "has-flag": { @@ -3024,21 +3055,21 @@ "dev": true }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } }, "supports-color": { @@ -3049,6 +3080,12 @@ "requires": { "has-flag": "^4.0.0" } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, @@ -3080,9 +3117,9 @@ } }, "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, "eslint-webpack-plugin": { @@ -3367,9 +3404,9 @@ } }, "flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, "foreground-child": { @@ -4447,12 +4484,24 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -5460,9 +5509,9 @@ } }, "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "regexpu-core": { @@ -6405,21 +6454,22 @@ } }, "table": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", - "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", "dev": true, "requires": { - "ajv": "^7.0.2", - "lodash": "^4.17.20", + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", - "string-width": "^4.2.0" + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "dependencies": { "ajv": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.1.tgz", - "integrity": "sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -6429,9 +6479,9 @@ } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "is-fullwidth-code-point": { @@ -6447,23 +6497,23 @@ "dev": true }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } } } @@ -6686,15 +6736,6 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, - "undercomplicate": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/undercomplicate/-/undercomplicate-0.1.1.tgz", - "integrity": "sha512-F/rOQ2x3YgH5YcEU+xUKxbv1GGF1znTrBi7MRvqtcDgwubNQ/HGLZbgZh2mHpX0z9AM0T2Pg3zqPwKWYK+VJhQ==", - "requires": { - "@hapi/topo": "^5.1.0", - "just-clone": "^3.2.1" - } - }, "underscore": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", @@ -6751,9 +6792,9 @@ "dev": true }, "v8-compile-cache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", - "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "verror": { diff --git a/package.json b/package.json index 0174877d..ff6e2ad7 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,11 @@ "docs": "./build_docs.sh" }, "dependencies": { + "@hapi/topo": "^5.1.0", "d3": "^5.16.0", "gwas-credible-sets": "^0.1.0", "just-clone": "^3.2.1", - "tabix-reader": "^1.0.1", - "undercomplicate": "^0.1.1" + "tabix-reader": "^1.0.1" }, "devDependencies": { "@babel/core": "^7.13.1", @@ -53,7 +53,7 @@ "babel-plugin-module-resolver": "^4.1.0", "chai": "^4.3.0", "clean-webpack-plugin": "^3.0.0", - "eslint": "^7.20.0", + "eslint": "^7.32.0", "eslint-webpack-plugin": "^2.5.2", "friendly-errors-webpack-plugin": "^1.7.0", "jsdoc": "^3.6.7", diff --git a/test/unit/components/data_layer/test_highlight_regions.js b/test/unit/components/data_layer/test_highlight_regions.js index 0f94c74c..255ce77d 100644 --- a/test/unit/components/data_layer/test_highlight_regions.js +++ b/test/unit/components/data_layer/test_highlight_regions.js @@ -16,14 +16,14 @@ describe('highlight_regions data layer', function () { assert.throws( () => DATA_LAYERS.create('highlight_regions', layout), /not both/, - 'Invalid layout options should trigger an error' + 'Invalid layout options should trigger an error', ); }); it('does not allow mouse interaction', function () { assert.throws( () => DATA_LAYERS.create('highlight_regions', { interaction: {}, behaviors: [] }), /mouse events/, - 'Invalid layout options should trigger an error' + 'Invalid layout options should trigger an error', ); }); }); diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index 047221ae..0859d542 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -22,7 +22,7 @@ describe('LocusZoom.DataLayer', function () { assert.throws( () => layer.getElementId({ not_much_to_go_on: 12 }), /Unable to generate/, - 'Cannot generate ID field if no value is present for that field' + 'Cannot generate ID field if no value is present for that field', ); }); @@ -40,14 +40,14 @@ describe('LocusZoom.DataLayer', function () { assert.throws( () => layer.getElementId({ some_field: 'carbon', other_field: '12' }), /Unable to generate/, - 'Not using a field name in the data, and expression is not recognized as a template' + 'Not using a field name in the data, and expression is not recognized as a template', ); layer.layout.id_field = ''; assert.throws( () => layer.getElementId({ some_field: 'carbon', other_field: '12' }), /Unable to generate/, - 'An empty template does not evaluate to a useful element ID' + 'An empty template does not evaluate to a useful element ID', ); }); }); diff --git a/test/unit/components/test_panel.js b/test/unit/components/test_panel.js index c0403333..f9208974 100644 --- a/test/unit/components/test_panel.js +++ b/test/unit/components/test_panel.js @@ -31,7 +31,7 @@ describe('Panel', function() { it('should fail if panel layout does not specify an ID', function() { assert.throws( () => this.plot.addPanel({ 'foo': 'bar' }), - /must specify "id"/ + /must specify "id"/, ); }); diff --git a/test/unit/components/test_plot.js b/test/unit/components/test_plot.js index eed6aa63..8696e198 100644 --- a/test/unit/components/test_plot.js +++ b/test/unit/components/test_plot.js @@ -469,7 +469,7 @@ describe('LocusZoom.Plot', function() { it('requires specifying one of two ways to subscribe', function () { assert.throws( () => this.plot.subscribeToData({ useless_option: 'something' }), - /must specify the desired data/ + /must specify the desired data/, ); }); @@ -495,7 +495,7 @@ describe('LocusZoom.Plot', function() { assert.throws( () => this.plot.subscribeToData({ from_layer: 'panel0.layer1' }, () => null), /unknown data layer/, - 'Warns if requesting an unknown layer' + 'Warns if requesting an unknown layer', ); }); @@ -504,7 +504,7 @@ describe('LocusZoom.Plot', function() { const listener = this.plot.subscribeToData( { namespace: { 'first': 'first' } }, - dataCallback + dataCallback, ); assert.ok(listener, 'Listener defined'); @@ -531,7 +531,7 @@ describe('LocusZoom.Plot', function() { from_layer: 'plot.panel1.layer1', onerror: errorCallback, }, - dataCallback + dataCallback, ); return this.plot.applyState() @@ -549,7 +549,7 @@ describe('LocusZoom.Plot', function() { from_layer: 'plot.panel1.layer1', onerror: errorCallback, }, - dataCallback + dataCallback, ); this.plot.off('data_from_layer', listener); @@ -557,7 +557,7 @@ describe('LocusZoom.Plot', function() { assert.notInclude( this.plot._event_hooks['data_from_layer'], listener, - 'Listener should not be registered' + 'Listener should not be registered', ); assert.ok(dataCallback.notCalled, 'listener success callback was not fired'); assert.ok(errorCallback.notCalled, 'listener error callback was not fired'); @@ -727,7 +727,7 @@ describe('LocusZoom.Plot', function() { assert.equal( filtered[0]['s:id'], 'b', - 'This item displayed is the one that satisfies matching rules' + 'This item displayed is the one that satisfies matching rules', ); }); }); diff --git a/test/unit/components/test_widgets.js b/test/unit/components/test_widgets.js index c5995f7e..9d3b44f2 100644 --- a/test/unit/components/test_widgets.js +++ b/test/unit/components/test_widgets.js @@ -144,7 +144,7 @@ describe('Toolbar widgets', function () { assert.equal( this.widget._getValue(), output, - `Number conversion should convert input ${input} to output ${output}` + `Number conversion should convert input ${input} to output ${output}`, ); }); }); diff --git a/test/unit/data/test_adapters.js b/test/unit/data/test_adapters.js index f1cfc869..c67a7b7a 100644 --- a/test/unit/data/test_adapters.js +++ b/test/unit/data/test_adapters.js @@ -21,11 +21,11 @@ describe('Data adapters', function () { it('warns when trying to create instance of (removed) old style adapters', function () { assert.throws( () => new BaseAdapter({}), - /have been replaced/ + /have been replaced/, ); assert.throws( () => new BaseApiAdapter({}), - /have been replaced/ + /have been replaced/, ); }); }); @@ -66,7 +66,7 @@ describe('Data adapters', function () { const source = new BaseTestClass({}); return source.getData({ _provider_name: 'sometest', - _test_data: [{ a: 1, b: 2 }]} + _test_data: [{ a: 1, b: 2 }]}, ).then((result) => assert.deepEqual(result, [{'sometest:a': 1, 'sometest:b': 2}])); }); @@ -74,7 +74,7 @@ describe('Data adapters', function () { const source = new BaseTestClass({ limit_fields: ['b'] }); return source.getData({ _provider_name: 'sometest', - _test_data: [{ a: 1, b: 2 }]} + _test_data: [{ a: 1, b: 2 }]}, ).then((result) => assert.deepEqual(result, [{ 'sometest:b': 2}])); }); @@ -87,7 +87,7 @@ describe('Data adapters', function () { assert.throws( () => source._findPrefixedKey([{'sometest:aaa': 1 }], 'no_such_key'), /Could not locate/, - 'Pedantically insists that data match the expected contract' + 'Pedantically insists that data match the expected contract', ); }); }); @@ -112,7 +112,7 @@ describe('Data adapters', function () { const adapter = new BaseUMAdapter({ prefix_namespace: false }); assert.throws( () => adapter._normalizeResponse({ a: [1, 2], b: [3] }), - /same length/ + /same length/, ); }); }); @@ -158,11 +158,11 @@ describe('Data adapters', function () { it('warns if the `data` key is missing', function () { assert.throws( () => new StaticSource([]), - /required option/ + /required option/, ); assert.throws( () => new StaticSource({}), - /required option/ + /required option/, ); }); @@ -220,13 +220,13 @@ describe('Data adapters', function () { assert.throws( () => source37._buildRequestOptions({ ldrefvar: '1:2_A/B' }, null), /must depend on/, - 'Warns if the adapter is totally misused (not part of a dependency chain)' + 'Warns if the adapter is totally misused (not part of a dependency chain)', ); assert.throws( () => source37._buildRequestOptions({ ldrefvar: '1:2_A/B' }, [{ some_other_data: true, meets_assoc_contract: false }]), /required key name/, - 'Warns if the adapter is totally misused (not given association data)' + 'Warns if the adapter is totally misused (not given association data)', ); }); diff --git a/test/unit/data/test_data_ops.js b/test/unit/data/test_data_ops.js index d00320a8..ed19fe0f 100644 --- a/test/unit/data/test_data_ops.js +++ b/test/unit/data/test_data_ops.js @@ -12,7 +12,7 @@ describe('Data operations', function() { const func = DATA_OPS.get('left_match'); assert.throws( () => func([1, 2, 3], 'paramA'), - /must receive exactly two recordsets/ + /must receive exactly two recordsets/, ); }); }); @@ -44,7 +44,7 @@ describe('Data operations', function() { [assoc_data, catalog_data], 'assoc:position', 'catalog:pos', - 'catalog:log_pvalue' + 'catalog:log_pvalue', ); assert.deepEqual(res, [ @@ -86,7 +86,7 @@ describe('Data operations', function() { [assoc_data, catalog_data], 'assoc:position', 'catalog:pos', - 'catalog:log_pvalue' + 'catalog:log_pvalue', ); assert.deepEqual(res, [ @@ -121,7 +121,7 @@ describe('Data operations', function() { [assoc_data, []], 'assoc:position', 'catalog:pos', - 'catalog:log_pvalue' + 'catalog:log_pvalue', ); assert.deepEqual(res, assoc_data); }); diff --git a/test/unit/data/test_requester.js b/test/unit/data/test_requester.js index 399f7e24..a4c43f0a 100644 --- a/test/unit/data/test_requester.js +++ b/test/unit/data/test_requester.js @@ -10,7 +10,7 @@ describe('Requester object defines and parses requests', function () { DATA_OPS.add('sumtwo', ( {plot_state: { state_field = 0 }}, [left, right], - some_param + some_param, ) => left + right + some_param + state_field); }); @@ -66,7 +66,7 @@ describe('Requester object defines and parses requests', function () { this._requester.config_to_sources({ 'somenamespace': 'nowhere' }, []); }, /not found in DataSources/, - 'Namespace references something not registered in datasource' + 'Namespace references something not registered in datasource', ); // Test duplicate namespace errors: joins @@ -76,7 +76,7 @@ describe('Requester object defines and parses requests', function () { [ {name: 'combined', type: 'left_match', requires: []}, {name: 'combined', type: 'left_match', requires: []}, - ] + ], ); }, /join name 'combined' must be unique/); @@ -86,10 +86,10 @@ describe('Requester object defines and parses requests', function () { {}, [ {name: 'combined', type: 'left_match', requires: ['unregistered', 'whatisthis']}, - ] + ], ); }, - /cannot operate on unknown provider/ + /cannot operate on unknown provider/, ); }); @@ -174,7 +174,7 @@ describe('Requester object defines and parses requests', function () { assert.deepEqual( data_operations, [{type: 'fetch', from: ['assoc', 'catalog', 'ld']}], // autogen doesn't specify dependencies, like ld(assoc) - 'Layout data_ops is mutated in place to reference namespaces (no dependencies assumed when auto-specifying)' + 'Layout data_ops is mutated in place to reference namespaces (no dependencies assumed when auto-specifying)', ); assert.deepEqual(dependencies, ['assoc', 'catalog', 'ld'], 'Dependencies are auto-guessed from namespaces'); @@ -188,7 +188,7 @@ describe('Requester object defines and parses requests', function () { { type: 'fetch', from: ['assoc', 'catalog', 'ld'] }, { type: 'sumtwo', name: 'somejoin', requires: ['assoc', 'ld'], params: [5] }, ], - 'Auto-generates fetch rules specifically; leaves other data ops untouched' + 'Auto-generates fetch rules specifically; leaves other data ops untouched', ); assert.deepEqual(dependencies, ['assoc', 'catalog', 'ld', 'somejoin(assoc, ld)'], 'Dependencies are (still) auto-guessed from namespaces'); }); @@ -201,7 +201,7 @@ describe('Requester object defines and parses requests', function () { assert.deepEqual( data_operations, [{type: 'fetch', from: ['assoc', 'catalog']}], - 'Layout data_ops is mutated in place to reference namespaces (no dependencies assumed when auto-specifying)' + 'Layout data_ops is mutated in place to reference namespaces (no dependencies assumed when auto-specifying)', ); assert.deepEqual(dependencies, ['assoc', 'catalog'], 'Dependencies take all namespaces into account'); }); @@ -222,7 +222,7 @@ describe('Requester object defines and parses requests', function () { { type: 'sumtwo', name: 'has_name', requires: ['assoc', 'catalog'], params: [] }, { type: 'sumtwo', name: 'join0', requires: ['assoc', 'has_name'], params: [] }, ], - 'Layout data_ops is mutated in place to give a name to any join without one' + 'Layout data_ops is mutated in place to give a name to any join without one', ); assert.deepEqual( diff --git a/test/unit/data/undercomplicate/test_adapters.js b/test/unit/data/undercomplicate/test_adapters.js new file mode 100644 index 00000000..f45c8d4d --- /dev/null +++ b/test/unit/data/undercomplicate/test_adapters.js @@ -0,0 +1,170 @@ +import {assert} from 'chai'; + +import {BaseAdapter, BaseUrlAdapter} from '../../../../esm/data/undercomplicate'; + + +class TestCacheQuirks extends BaseAdapter { + _getCacheKey(options) { + return options.somevalue; + } + + _performRequest(options) { + // Return an object (not a string!) so that cache returns something at risk of being mutated + return Promise.resolve([{ a: 1, b:2, c:3 }]); + } + + _normalizeResponse(records, options) { + // No parsing required + return records; + } + + _annotateRecords(records, options) { + // Mutate the returned object, to confirm it doesn't mutate the contents of cache by shared reference + records.forEach((row) => row.a += 1); + return records; + } +} + +class TestAdapter extends BaseAdapter { + _buildRequestOptions(options, dependent_data) { + const somevalue = (dependent_data ? dependent_data.length : options.somevalue); + return { somevalue }; + } + + _getCacheKey(options) { + return options.somevalue; + } + + _performRequest(options) { + return Promise.resolve('line1\tcol2\nline2\tcol2'); + } + + _normalizeResponse(response_text, options) { + return response_text.split('\n') + .map((row) => { + const [a, b] = row.split('\t'); + return {a, b}; + }); + } + + _annotateRecords(records, options) { + records.forEach((row) => row.c = true); + return records; + } + + _postProcessResponse(records, options) { + return records.map((record) => { + return Object.keys(record).reduce((acc, akey) => { + acc[`prefix.${akey}`] = record[akey]; + return acc; + }, {}); + }); + } +} + + +describe('BaseAdapter', function () { + it('performs a sequence of operations on retrieved data', function () { + const source = new TestAdapter(); + return source.getData() + .then((result) => { + assert.deepEqual(result, [ + {'prefix.a': 'line1', 'prefix.b': 'col2', 'prefix.c': true}, + {'prefix.a': 'line2', 'prefix.b':'col2', 'prefix.c': true}, + ]); + }); + }); + + it('can consider dependent data when performing request', function () { + const source = new TestAdapter(); + const base_options = {somevalue: 12}; + + // First trigger a request that leaves behind a trace of the default options + return source.getData(base_options) + .then((result) => { + assert.ok(source._cache.has(12), 'Uses a cache value as given'); + + // Then trigger a request that leaves behind a trace of using dependent data + return source.getData(base_options, [1, 2, 3]); + }).then((result) => { + assert.ok(source._cache.has(3), 'Uses a cache value calculated from dependent data'); + }); + }); + + it('uses a cache with configurable size', function () { + const source = new TestAdapter({cache_size: 2}); + const requests = [1, 2, 3].map((item) => source.getData({somevalue: item})); + return Promise.all(requests).then(() => { + assert.equal(source._cache._cur_size, 2); + }); + }); + + it('intelligently clones non-string cache entries', function () { + const source = new TestCacheQuirks(); + return source.getData({ somevalue: 1 }) + .then((result) => { + assert.deepEqual(result, [{ a: 2, b:2, c:3 }], 'First cache check returns correct result'); + return source.getData({ somevalue: 1 }); + }) + .then((result) => assert.deepEqual(result, [{ a: 2, b:2, c:3 }], 'Second cache check returns correct result')); + }); + + it('does not mangle string values in cache', function () { + const expected = 'Some string value'; + + class TestStringCache extends BaseAdapter { + _getCacheKey(options) { + return options.somevalue; + } + + _performRequest(options) { + return Promise.resolve(expected); + } + } + + const source = new TestStringCache(); + return source.getData({ somevalue: 1 }) + .then((result) => { + assert.deepEqual(result, expected, 'First value is returned and second cache'); + return source.getData({ somevalue: 1 }); + }) + .then((result) => assert.deepEqual(result, expected, 'Second request returns cache hit, not mangled by cloning process')); + }); + + it('unsets a cached promise if the promise rejects', function () { + let some_counter = 0; + + class TestCacheRejection extends BaseAdapter { + _getCacheKey(options) { + return 'always_same_key'; + } + + _performRequest(options) { + // If we were feeding (failed) response from cache, then every new request would have the same counter value + some_counter += 1; + return Promise.reject(some_counter); + } + } + + const source = new TestCacheRejection(); + return source.getData({}) + .catch((value_from_error) => { + assert.equal(value_from_error, 1, 'First request is counted as 1'); + return source.getData({}); + }).catch((value_from_error) => { + assert.equal(value_from_error, 2, 'Second request is counted as 2 because rejections are removed from cache'); + }); + }); +}); + + + +describe('BaseURLAdapter', function () { + it('Requests throw an error when a URL is not provided', function () { + const source = new BaseUrlAdapter({}); + assert.throws( + () => source._performRequest({}), + /must specify a resource URL/, + ); + }); +}); diff --git a/test/unit/data/undercomplicate/test_cache.js b/test/unit/data/undercomplicate/test_cache.js new file mode 100644 index 00000000..ed6df70c --- /dev/null +++ b/test/unit/data/undercomplicate/test_cache.js @@ -0,0 +1,83 @@ +import {assert} from 'chai'; + +import {LRUCache} from '../../../../esm/data/undercomplicate'; + +describe('LRU cache', function () { + it('restricts max size by evicting old items', function () { + const cache = new LRUCache(3); + ['a', 'b', 'c', 'd', 'e'].forEach((item, index) => cache.add(item, index)); + + assert.equal(cache._cur_size, 3, 'Wrong number of cache entries'); + assert.sameOrderedMembers([...cache._store.keys()], ['c', 'd', 'e'], 'Incorrect cache members'); + }); + + it('does not cache if max size is 0', function () { + const cache = new LRUCache(0); + ['a', 'b', 'c', 'd', 'e'].forEach((item, index) => cache.add(item, index)); + + assert.equal(cache._cur_size, 0, 'No items cached'); + assert.isNull(cache._head, 'No head node'); + assert.isNull(cache._tail, 'No tail node'); + }); + + it('does not support "negative number for infinite cache"', function () { + assert.throws( + () => new LRUCache(-12), + /must be >= 0/, + ); + }); + + it('promotes cache entries by most recently read', function () { + const cache = new LRUCache(3); + ['a', 'b', 'c', 'a'].forEach((item, index) => cache.add(item, index)); + + assert.equal(cache._cur_size, 3, 'Wrong number of cache entries'); + assert.equal(cache._head.key, 'a', 'Last item accessed is at head'); + assert.equal(cache._tail.key, 'b', 'LRU is at tail'); + + cache.get('b'); + assert.equal(cache._head.key, 'b', 'Accessing another item updates head'); + + cache.get('nothing'); + assert.equal(cache._head.key, 'b', 'Uncached values will not affect the LRU order'); + }); + + it('can remove an item by key name', function () { + const cache = new LRUCache(3); + cache.add('some_key', 12); + + let result = cache.remove('some_key'); + assert.ok(result, 'Removing a known item returns true'); + assert.equal(cache._cur_size, 0, 'Item removed from cache'); + + result = cache.remove('never_there'); + assert.notOk(result, 'Removing unknown item returns false'); + assert.equal(cache._cur_size, 0, 'Cache still has zero items'); + + }); + + it('stores metadata along with cache entries', function () { + const cache = new LRUCache(3); + + const meta = {chr: '1', start: 1, end: 100}; + cache.add('something', 12, meta); + + assert.deepEqual(cache._head.metadata, meta); + }); + + it('can search for an item', function () { + const cache = new LRUCache(3); + + cache.add('akey', 12, {chr: '2', start: 15, end: 30}); + cache.add('bkey', 18, {chr: '1', start: 10, end: 20}); + + let found = cache.find((node) => node.value < 10); + assert.equal(found, null, 'Return null when no match found'); + + found = cache.find((node) => node.value > 10); + assert.equal(found.key, 'bkey', 'Return the first match (ordered by most newest cache entry)'); + + found = cache.find(({ metadata}) => metadata.chr === '2' && 16 >= metadata.start && 18 <= metadata.end); + assert.deepEqual(found.key, 'akey', 'A more interesting example: region overlap tested via metadata'); + }); +}); diff --git a/test/unit/data/undercomplicate/test_joins.js b/test/unit/data/undercomplicate/test_joins.js new file mode 100644 index 00000000..bff59d3d --- /dev/null +++ b/test/unit/data/undercomplicate/test_joins.js @@ -0,0 +1,57 @@ +import {assert} from 'chai'; + +import {left_match, inner_match, full_outer_match} from '../../../../esm/data/undercomplicate/joins'; + + +describe('Data Join Helpers', function() { + beforeEach(function () { + this.left_data = [ + { gene_id: 'ENSG00000148737', pval: .05 }, + { gene_id: 'ENSG00000012048', pval: .0005 }, // not on right + ]; + + this.right_data = [ + { gene_id: 'ENSG00000148737', catalog: true }, // 2 matches for 1 item on left + { gene_id: 'ENSG00000148737', catalog: false }, // ' + { gene_id: 'ENSG00000128731', catalog: true }, // Not on left + ]; + }); + + describe('left join helper', function () { + it('takes all records on left, merged with 1 or more records on right', function () { + const expected = [ + { gene_id: 'ENSG00000148737', pval: .05, catalog: true }, + { gene_id: 'ENSG00000148737', pval: .05, catalog: false }, + { gene_id: 'ENSG00000012048', pval: .0005 }, + ]; + + const actual = left_match(this.left_data, this.right_data, 'gene_id', 'gene_id'); + assert.deepEqual(actual, expected); + }); + }); + + describe('inner join helper', function () { + it('takes only records with matches on both left and right', function () { + const expected = [ + { gene_id: 'ENSG00000148737', pval: .05, catalog: true }, + { gene_id: 'ENSG00000148737', pval: .05, catalog: false }, + ]; + const actual = inner_match(this.left_data, this.right_data, 'gene_id', 'gene_id'); + assert.deepEqual(actual, expected); + }); + }); + + describe('full outer join helper', function () { + it('takes all records, matching them where suitable', function () { + const expected = [ + { gene_id: 'ENSG00000148737', pval: .05, catalog: true }, + { gene_id: 'ENSG00000148737', pval: .05, catalog: false }, + { gene_id: 'ENSG00000012048', pval: .0005 }, + { gene_id: 'ENSG00000128731', catalog: true }, + ]; + + const actual = full_outer_match(this.left_data, this.right_data, 'gene_id', 'gene_id'); + assert.deepEqual(actual, expected); + }); + }); +}); diff --git a/test/unit/data/undercomplicate/test_requests.js b/test/unit/data/undercomplicate/test_requests.js new file mode 100644 index 00000000..e817e015 --- /dev/null +++ b/test/unit/data/undercomplicate/test_requests.js @@ -0,0 +1,79 @@ +import {assert} from 'chai'; + +import {_parse_declaration, getLinkedData} from '../../../../esm/data/undercomplicate/requests'; + + +class SequenceFixture { + getData(options, ...prior) { + return prior.reduce((acc, val) => (acc += val), 1); + } +} + +describe('Request helpers', function () { + describe('Dependency parsing syntax', function () { + it('parses requests with and without dependencies', function () { + let result = _parse_declaration('record'); + assert.deepEqual(result, ['record', []]); + + result = _parse_declaration('record(a)'); + assert.deepEqual(result, ['record', ['a']]); + + result = _parse_declaration('record(a, b)'); + assert.deepEqual(result, ['record', ['a', 'b']]); + + result = _parse_declaration('record_12(a, a1)'); + assert.deepEqual(result, ['record_12', ['a', 'a1']]); + }); + + it('rejects invalid syntax', function () { + assert.throws(() => _parse_declaration('dependency.name'), /Unable to parse/); + + assert.throws(() => _parse_declaration('one_dep another_thing'), /Unable to parse/); + }); + }); + + describe('getLinkedData', function () { + it('chains requests together while respecting dependencies', function () { + const entities = new Map(); + const dependencies = ['a', 'b(a)', 'c(a,b)', 'd(c)']; + const shared_state = new SequenceFixture(); + ['a', 'b', 'c', 'd'] + .forEach((key) => entities.set(key, shared_state)); + + // The last result in the chain, by itself, represents a sum of all prior + return getLinkedData({}, entities, dependencies) + .then((result) => assert.equal(result, 5)); + }); + + it('chains requests and can return all values', function () { + const entities = new Map(); + const dependencies = ['a', 'b(a)', 'c(a,b)', 'd(c)']; + const shared_state = new SequenceFixture(); + ['a', 'b', 'c', 'd'] + .forEach((key) => entities.set(key, shared_state)); + + // The last result in the chain, by itself, represents a sum of all prior + return getLinkedData({}, entities, dependencies, false) + .then((result) => assert.deepEqual(result, [1, 2, 4, 5])); + }); + + it('warns if spec references a non-existent provider', function () { + assert.throws( + () => getLinkedData({}, new Map(), ['a']), + /no matching source was provided/, + ); + }); + + it('warns if circular dependencies were declared', function () { + assert.throws( + () => getLinkedData({}, new Map(), ['a(b)', 'b(a)']), + /circular dependency/, + ); + + assert.throws( + () => getLinkedData({}, new Map(), ['a(a)']), + /circular dependency/, + ); + }); + }); +}); diff --git a/test/unit/ext/lz-parsers/gwas/test_sniffers.js b/test/unit/ext/lz-parsers/gwas/test_sniffers.js index ce313851..3150740a 100644 --- a/test/unit/ext/lz-parsers/gwas/test_sniffers.js +++ b/test/unit/ext/lz-parsers/gwas/test_sniffers.js @@ -195,7 +195,7 @@ describe('guessGWAS format detection', () => { is_neg_log_pvalue: false, beta_col: 8, stderr_beta_col: 9, - } + }, ); }); @@ -215,7 +215,7 @@ describe('guessGWAS format detection', () => { alt_col: 7, pvalue_col: 9, is_neg_log_pvalue: false, - } + }, ); }); @@ -235,7 +235,7 @@ describe('guessGWAS format detection', () => { is_neg_log_pvalue: false, beta_col: 8, stderr_beta_col: 9, - } + }, ); }); @@ -254,7 +254,7 @@ describe('guessGWAS format detection', () => { pvalue_col: 17, is_neg_log_pvalue: false, beta_col: 16, - } + }, ); }); @@ -274,7 +274,7 @@ describe('guessGWAS format detection', () => { pvalue_col: 16, is_neg_log_pvalue: false, beta_col: 15, - } + }, ); }); @@ -292,7 +292,7 @@ describe('guessGWAS format detection', () => { is_neg_log_pvalue: false, beta_col: 9, stderr_beta_col: 10, - } + }, ); }); @@ -314,7 +314,7 @@ describe('guessGWAS format detection', () => { is_neg_log_pvalue: false, beta_col: 9, stderr_beta_col: 10, - } + }, ); }); @@ -334,7 +334,7 @@ describe('guessGWAS format detection', () => { is_neg_log_pvalue: false, beta_col: 12, stderr_beta_col: 13, - } + }, ); }); @@ -351,7 +351,7 @@ describe('guessGWAS format detection', () => { is_neg_log_pvalue: false, beta_col: 4, stderr_beta_col: 5, - } + }, ); }); }); diff --git a/test/unit/ext/test_dynamic-urls.js b/test/unit/ext/test_dynamic-urls.js index fb89e6de..8e539cc6 100644 --- a/test/unit/ext/test_dynamic-urls.js +++ b/test/unit/ext/test_dynamic-urls.js @@ -32,14 +32,14 @@ describe('LzDynamicUrls', function() { const query = '?numeric=1&boolean=false&short_boolean=0&no_value'; const plotData = paramsFromUrl( { numeric: 'numeric', boolean: 'boolean', short_boolean: 'short_boolean', no_value: 'no_value' }, - query + query, ); // Hack: deepStrictEqual behaving oddly in various scenarios; compare types separately assert.deepEqual( plotData, {numeric: '1', boolean: 'false', short_boolean: '0', no_value: ''}, - 'Numeric value deserialized as string' + 'Numeric value deserialized as string', ); assert.ok(typeof plotData['numeric'] === 'string', 'Numeric field represented as string'); diff --git a/test/unit/ext/test_ext_intervals-track.js b/test/unit/ext/test_ext_intervals-track.js index 04cac5d2..f78c6a27 100644 --- a/test/unit/ext/test_ext_intervals-track.js +++ b/test/unit/ext/test_ext_intervals-track.js @@ -52,7 +52,7 @@ describe('Interval annotation track', function () { assert.deepEqual( this.color_config, // Note we are comparing base layout (before auto-gen) to dynamic layout (after) - find_color_options(this.instance.layout) + find_color_options(this.instance.layout), ); assert.deepEqual(this.instance.layout.legend, this.instance._base_layout.legend); }); @@ -89,7 +89,7 @@ describe('Interval annotation track', function () { { color: 'rgb(0,255,0)', 'intervals:state_id': 2, label: 'Weak enhancer', shape: 'rect', 'width': 9 }, { color: 'rgb(0,0,255)', 'intervals:state_id': 3, label: 'Strong enhancer', shape: 'rect', 'width': 9 }, ], - 'Legend items map the correct stateID and colors together' + 'Legend items map the correct stateID and colors together', ); }); @@ -113,7 +113,7 @@ describe('Interval annotation track', function () { assert.deepEqual( final_colors.parameters.values, ['rgb(212,212,212)', 'rgb(192,192,192)', 'rgb(128,128,128)', 'rgb(189,183,107)', 'rgb(233,150,122)', 'rgb(205,92,92)', 'rgb(138,145,208)', 'rgb(102,205,170)', 'rgb(255,255,0)', 'rgb(194,225,5)', 'rgb(0,100,0)', 'rgb(0,128,0)', 'rgb(50,205,50)', 'rgb(255,69,0)', 'rgb(255,0,0)'], - 'The smallest preset color scheme that fits data size will be used' + 'The smallest preset color scheme that fits data size will be used', ); assert.deepEqual( final_legend, @@ -122,7 +122,7 @@ describe('Interval annotation track', function () { { color: 'rgb(192,192,192)', 'intervals:state_id': 2, label: 'Weak enhancer', shape: 'rect', 'width': 9 }, { color: 'rgb(128,128,128)', 'intervals:state_id': 3, label: 'Strong enhancer', shape: 'rect', 'width': 9 }, ], - 'Legend items map the correct stateID and colors together' + 'Legend items map the correct stateID and colors together', ); }); }); diff --git a/test/unit/ext/test_tabix-source.js b/test/unit/ext/test_tabix-source.js index 2c33a9b0..15def0bf 100644 --- a/test/unit/ext/test_tabix-source.js +++ b/test/unit/ext/test_tabix-source.js @@ -24,14 +24,14 @@ describe('TabixUrlSource', function () { it('requires a parser function', function () { assert.throws( () => ADAPTERS.create('TabixUrlSource', { url_data: 'ok.tbi' }), - /missing required configuration/ + /missing required configuration/, ); }); it('Checks that overfetch is provided as a fraction', function () { assert.throws( () => ADAPTERS.create('TabixUrlSource', { url_data: 'ok.tbi', parser_func: () => 12, overfetch: 99 }), - /fraction/ + /fraction/, ); }); diff --git a/test/unit/helpers/test_jsonpath.js b/test/unit/helpers/test_jsonpath.js index 792c5f2a..e0b76e3f 100644 --- a/test/unit/helpers/test_jsonpath.js +++ b/test/unit/helpers/test_jsonpath.js @@ -51,7 +51,7 @@ describe('jsonpath query syntax', function () { assert.throws( () => query(sample_data, '$.panels[@.id !== "a" && @.id === "b"]'), /Cannot parse/, - 'Warns on expression' + 'Warns on expression', ); }); @@ -59,7 +59,7 @@ describe('jsonpath query syntax', function () { assert.throws( () => query(sample_data, '$.panels[1]'), /Cannot parse/, - "Numeric indices are not allowed, because the whole point of this syntax is to write queries that won't break if the order of panels changes later" + "Numeric indices are not allowed, because the whole point of this syntax is to write queries that won't break if the order of panels changes later", ); }); }); diff --git a/test/unit/helpers/test_layouts.js b/test/unit/helpers/test_layouts.js index 4c752666..4b1cd32a 100644 --- a/test/unit/helpers/test_layouts.js +++ b/test/unit/helpers/test_layouts.js @@ -42,7 +42,7 @@ describe('Layout helper functions', function () { assert.sameMembers( result, ['assoc:nearest_gene', 'assoc:rsid'], - 'Finds all unique valid field names' + 'Finds all unique valid field names', ); }); @@ -63,7 +63,7 @@ describe('Layout helper functions', function () { assert.sameMembers( result, ['phewas:trait_group'], - 'Finds nested field names' + 'Finds nested field names', ); }); }); diff --git a/test/unit/helpers/test_render.js b/test/unit/helpers/test_render.js index 6e3f6f0f..1a6f670d 100644 --- a/test/unit/helpers/test_render.js +++ b/test/unit/helpers/test_render.js @@ -43,7 +43,7 @@ describe('Coordinate coalescing', function () { const actual = coalesce_scatter_points( this.sample_data, -Infinity, Infinity, 3, - 0, 1, Infinity + 0, 1, Infinity, ); const expected = _addSymbols([ { x: 1, y: 0.9 }, @@ -61,7 +61,7 @@ describe('Coordinate coalescing', function () { const actual = coalesce_scatter_points( this.sample_data, -Infinity, Infinity, 1, - 0, 1, Infinity + 0, 1, Infinity, ); const expected = _addSymbols([ { x: 0, y: 0.5 }, @@ -82,7 +82,7 @@ describe('Coordinate coalescing', function () { const actual = coalesce_scatter_points( this.sample_data, -Infinity, Infinity, 1.75, // From sample data: verify that x = 0.5 and x = 2 don't combine - 0, 1, Infinity + 0, 1, Infinity, ); const expected = _addSymbols([ { x: 0, y: 0.5 }, @@ -102,7 +102,7 @@ describe('Coordinate coalescing', function () { const actual = coalesce_scatter_points( this.sample_data, -Infinity, Infinity, 3, - 0.0, 1, 0.5 + 0.0, 1, 0.5, ); const expected = _addSymbols([ { x: 1, y: 0.9 }, @@ -126,7 +126,7 @@ describe('Coordinate coalescing', function () { const actual = coalesce_scatter_points( sample_data, -Infinity, Infinity, 3, - 0, 1, Infinity + 0, 1, Infinity, ); assert.deepStrictEqual(actual, sample_data, 'Significant points do not coalesce'); }); diff --git a/test/unit/test_layouts.js b/test/unit/test_layouts.js index 55c2c397..8cdcc4d7 100644 --- a/test/unit/test_layouts.js +++ b/test/unit/test_layouts.js @@ -193,11 +193,11 @@ describe('Layout helpers', function () { it('warns if layout or namespace-overrides are not objects', function () { assert.throws( () => applyNamespaces(null, {}), - /as objects/ + /as objects/, ); assert.throws( () => applyNamespaces({}, 42), - /as objects/ + /as objects/, ); assert.ok(applyNamespaces({}), 'If no namespaced provided, default value is used'); }); diff --git a/test/unit/test_transforms.js b/test/unit/test_transforms.js index 01fe73f0..ebe7c4ce 100644 --- a/test/unit/test_transforms.js +++ b/test/unit/test_transforms.js @@ -42,7 +42,7 @@ describe('Transformation Functions', function() { it('should escape characters with special meaning in xml, and ignore others', function() { assert.equal( htmlescape(""), - '<script type="application/javascript">alert('yo & ' + `I`)</script>' + '<script type="application/javascript">alert('yo & ' + `I`)</script>', ); }); }); From 4475ff0ca75f848efb1f455d2f2fddc8897f05c5 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 27 Jan 2022 21:39:08 -0500 Subject: [PATCH 094/100] Update docs with a nicer example of customizing the request. Key it to something that regular users might want to do by default. --- README.md | 15 +++++++-------- assets/docs/data_retrieval.md | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 94ed192d..c5fceb37 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ To see functional examples of plots generated with LocusZoom.js see [statgen.git ![LocusZoom.js Standard Association Plot](examples/locuszoom_standard_association_example.png) -## Making a LocusZoom Plot +## Making a LocusZoom Plot: Quickstart tutorial ### 1. Include Necessary JavaScript and CSS -The page you build that embeds the LocusZoom plugin must include the following resources, found in the `dist` directory (or via CDN): +The page you build that embeds the LocusZoom plugin must include the following resources, found in the `dist` directory (or preferably loaded via CDN): * `d3.js` [D3.js](https://d3js.org/) v5.16.0 is used to draw graphics in LocusZoom plots. It may be loaded [via a CDN](https://cdn.jsdelivr.net/npm/d3@^5.16.0). It must be present before LocusZoom is loaded. @@ -178,15 +178,15 @@ LocusZoom.Layouts.get("data_layer", "genes", overrides); #### 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: +**State** is JSON-serializable object containing information that can affect the entire plot (including all data retrieval requests). State can be set before or after the plot is initialized. For example, the following special-named fields will cause the plot to be loaded to a specific region of interest on first render: ```javascript const layout = LocusZoom.Layouts.get('plot', 'standard_association', { state: { chr: 6, start: 20379709, end: 20979709 } }) ``` -#### Predefining State With `data-region` +#### Alternate: setting the initial view via `data-region` -You can also describe the locus query aspect of the State (chromosome, start, and end position) using a `data-region` attribute of the containing element before populating it, like so: +You can also describe the locususing a `data-region` attribute of the containing element before populating it, like so: ```html
    @@ -195,7 +195,6 @@ You can also describe the locus query aspect of the State (chromosome, start, an When `LocusZoom.populate()` is executed on the element defined above it will automatically parse any `data-region` parameter to convert those values into the initial state. ## Development Setup - ### Dependencies LocusZoom is an entirely client-side library designed to plug into arbitrary data sets, be they local files, APIs, or something else entirely. It has the following external dependencies: @@ -221,8 +220,8 @@ This build process will also write sourcemaps, to help with debugging code even * `npm run test` - Run unit tests (optional: `npm run test:coverage` to output a code coverage report) * `npm run dev` - Automatically rebuild the library whenever code changes (development mode) * `npm run build` - Run tests, and if they pass, build the library for release -* `npm run css` - Rebuild the CSS using SASS -* `npm run docs` - Build the library documentation +* `npm run css` - Rebuild the CSS using SASS (CSS rarely changes, so this doesn't get done automatically in dev mode) +* `npm run docs` - Build just the library documentation #### Automated Testing diff --git a/assets/docs/data_retrieval.md b/assets/docs/data_retrieval.md index 19ce6e4a..91d0de0e 100644 --- a/assets/docs/data_retrieval.md +++ b/assets/docs/data_retrieval.md @@ -166,7 +166,7 @@ The built-in LocusZoom adapters can be used as-is in many cases, so long as the 2. If the actual headers, body, or request method must be customized in order to carry out the request. This happens when data is retrieved from a particular technology (REST vs GraphQL vs Tabix), or if the request must incorporate some form of authentication credentials. 3. If some of the fields in your custom API format need to be transformed or renamed in order to match expectations in LZ code. For example, the LD adapter may try to suggest an LD reference variant by looking for the GWAS variant with the largest `log_pvalue`. (over time, built-in LZ adapters trend towards being more strict about field names; it is easier to write reliable code when not having to deal with unpredictable data!) -## Re-using code via subclasses +## Customizing data retrieval via subclasses Most custom sites will only need to change very small things to work with their data. For example, if your REST API uses the same payload format as the UM PortalDev API, but a different way of constructing queries, you can change just one function and define a new data adapter: ```javascript @@ -174,11 +174,23 @@ const AssociationLZ = LocusZoom.Adapters.get('AssociationLZ'); class CustomAssociation extends AssociationLZ { _getURL(request_options) { // Every adapter receives the info from plot.state, plus any additional request options calculated/added in the function `_buildRequestOptions` - // The inputs to the function can be used to influence what query is constructed. Eg, since the current view region is stored in `plot.state`: + // The inputs to the function can be used to influence what query is constructed. Eg, since the current view region is stored in `plot.state`: const {chr, start, end} = request_options; // Fetch the region of interest from a hypothetical REST API that uses query parameters to define the region query, for a given study URL such as `data.example/gwas//?chr=_&start=_&end=_` return `${this._url}/${this.source}/?chr=${encodeURIComponent(chr)}&start=${encodeURIComponent(start)}&end${encodeURIComponent(end)}` } + + _annotateRecords(records, options) { + // Imagine that your API returns a field called pValue (not recommended due to numerical underflow!)... + // but LZ generally expects a field called `log_pvalue` (because it is used to make connections between data, this is one of the few field names we are somewhat strict about: this is the -log10(pvalue)). + // Since many special features (like "find best hit for LD") depend on this field, we can manually + // add the desired field name using custom code that acts on the normalized, parsed records from the server + + // Ideally, your service should send the -log10(pvalue) directly, because very significant hits will get rounded to 0 once they reach the browser, and sending them as log_pvalue will ensure that your results are displayed correctly without truncation. (underflow is a basic limitation of javascript) + // But sometimes, you need to work with a server that made a different choice, or you want to add a calculated or transformed field. This helper method is a place to perform any required cleanup. + records.forEach((item) => item.log_pvalue = -Math.log10(item.pValue)); + return records; + } } // A custom adapter should be added to the registry before using it LocusZoom.Adapters.add('CustomAssociation', CustomAssociation); From 8b02e9d1ecc840894afab5cce7c7801420c972f1 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 27 Jan 2022 22:33:04 -0500 Subject: [PATCH 095/100] Tweak docs to present both special fields required by Association sources, for true drop in usage --- assets/docs/data_retrieval.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/assets/docs/data_retrieval.md b/assets/docs/data_retrieval.md index 91d0de0e..ad61492d 100644 --- a/assets/docs/data_retrieval.md +++ b/assets/docs/data_retrieval.md @@ -181,14 +181,13 @@ class CustomAssociation extends AssociationLZ { } _annotateRecords(records, options) { - // Imagine that your API returns a field called pValue (not recommended due to numerical underflow!)... - // but LZ generally expects a field called `log_pvalue` (because it is used to make connections between data, this is one of the few field names we are somewhat strict about: this is the -log10(pvalue)). - // Since many special features (like "find best hit for LD") depend on this field, we can manually - // add the desired field name using custom code that acts on the normalized, parsed records from the server - - // Ideally, your service should send the -log10(pvalue) directly, because very significant hits will get rounded to 0 once they reach the browser, and sending them as log_pvalue will ensure that your results are displayed correctly without truncation. (underflow is a basic limitation of javascript) - // But sometimes, you need to work with a server that made a different choice, or you want to add a calculated or transformed field. This helper method is a place to perform any required cleanup. - records.forEach((item) => item.log_pvalue = -Math.log10(item.pValue)); + // Imagine that your API returns a payload that mostly works, but a few fields are a little different than what is expected. + // Usually LocusZoom is pretty tolerant of changing field names- but because Association plots connect to so many extra annotations, certain magic features require a little extra attention to detail + records.forEach((item) => { + // LocusZoom connects assoc summstats to other datasets, by finding the most significant variant. To find that variant and ask for LD, it expects two special fields with very specific names, and sort of specific formats. If you already have these fields, great- if not, they can be manually added by custom code, even if no one will let you change the API server directly + item.log_pvalue = -Math.log10(item.pValue); // It's better if you send log_pvalue from the server directly, not calculate it on the front end (JS is subject to numerical underflow, and sending pValue to the browser may cause numerical underflow problems) + item.variant = `${item.chrom}:${item.pos}_${item.ref}/${item.alt}`; + }); return records; } } From 3774a399d309a923132b3c42f95dd2aa758bf1ed Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 27 Jan 2022 23:00:38 -0500 Subject: [PATCH 096/100] Clean up unused field name (leftover from refactor) --- examples/multiple_phenotypes_layered.html | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/multiple_phenotypes_layered.html b/examples/multiple_phenotypes_layered.html index 6bdc43e4..9debe7d7 100644 --- a/examples/multiple_phenotypes_layered.html +++ b/examples/multiple_phenotypes_layered.html @@ -106,7 +106,6 @@

    Top Hits

    phenos.forEach(function(pheno){ data_sources.add(pheno.namespace, ["AssociationLZ", {url: apiBase + "statistic/single/", source: pheno.study_id }]); var association_data_layer_mods = { - join_options: [], id: "associationpvalues_" + pheno.namespace, name: pheno.title, point_shape: "circle", From 4247c68204d20f43569aebc94050287c1058d127 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Thu, 27 Jan 2022 23:42:15 -0500 Subject: [PATCH 097/100] Bump and add assets for 0.14.0-beta.3 --- dist/ext/lz-aggregation-tests.min.js | 2 +- dist/ext/lz-credible-sets.min.js | 2 +- dist/ext/lz-credible-sets.min.js.map | 2 +- dist/ext/lz-dynamic-urls.min.js | 2 +- dist/ext/lz-forest-track.min.js | 2 +- dist/ext/lz-intervals-enrichment.min.js | 2 +- dist/ext/lz-intervals-track.min.js | 2 +- dist/ext/lz-parsers.min.js | 2 +- dist/ext/lz-parsers.min.js.map | 2 +- dist/ext/lz-tabix-source.min.js | 2 +- dist/ext/lz-widget-addons.min.js | 2 +- dist/ext/lz-widget-addons.min.js.map | 2 +- dist/locuszoom.app.min.js | 4 +- dist/locuszoom.app.min.js.map | 2 +- docs/api/LLNode.html | 342 ++++ docs/api/LayoutRegistry.html | 2 +- docs/api/Line.html | 2 +- docs/api/Panel.html | 2 +- docs/api/Plot.html | 2 +- docs/api/TransformationFunctionsRegistry.html | 2 +- ...onents_data_layer_annotation_track.js.html | 2 +- docs/api/components_data_layer_arcs.js.html | 2 +- docs/api/components_data_layer_base.js.html | 6 +- docs/api/components_data_layer_genes.js.html | 4 +- ...nents_data_layer_highlight_regions.js.html | 2 +- docs/api/components_data_layer_line.js.html | 2 +- .../api/components_data_layer_scatter.js.html | 2 +- docs/api/components_legend.js.html | 2 +- docs/api/components_panel.js.html | 2 +- docs/api/components_plot.js.html | 4 +- docs/api/components_toolbar_index.js.html | 2 +- docs/api/components_toolbar_widgets.js.html | 2 +- docs/api/data_adapters.js.html | 48 +- docs/api/data_field.js.html | 2 +- docs/api/data_requester.js.html | 4 +- docs/api/data_sources.js.html | 2 +- docs/api/data_undercomplicate_adapter.js.html | 256 +++ docs/api/data_undercomplicate_index.js.html | 68 + docs/api/data_undercomplicate_joins.js.html | 160 ++ .../data_undercomplicate_lru_cache.js.html | 214 +++ .../api/data_undercomplicate_requests.js.html | 150 ++ docs/api/data_undercomplicate_util.js.html | 70 + docs/api/ext_lz-credible-sets.js.html | 6 +- docs/api/ext_lz-dynamic-urls.js.html | 2 +- docs/api/ext_lz-forest-track.js.html | 2 +- docs/api/ext_lz-intervals-enrichment.js.html | 2 +- docs/api/ext_lz-intervals-track.js.html | 2 +- docs/api/ext_lz-parsers_bed.js.html | 2 +- docs/api/ext_lz-parsers_gwas_parsers.js.html | 4 +- docs/api/ext_lz-parsers_index.js.html | 2 +- docs/api/ext_lz-parsers_ld.js.html | 2 +- docs/api/ext_lz-tabix-source.js.html | 2 +- docs/api/ext_lz-widget-addons.js.html | 2 +- docs/api/global.html | 448 ++++- docs/api/helpers_common.js.html | 4 +- docs/api/helpers_display.js.html | 2 +- docs/api/helpers_jsonpath.js.html | 2 +- docs/api/helpers_layouts.js.html | 6 +- docs/api/helpers_render.js.html | 2 +- docs/api/helpers_scalable.js.html | 2 +- docs/api/helpers_transforms.js.html | 2 +- docs/api/index.html | 18 +- docs/api/index.js.html | 2 +- docs/api/layouts_index.js.html | 8 +- docs/api/module-LocusZoom-DataSources.html | 2 +- docs/api/module-LocusZoom.html | 2 +- ...dule-LocusZoom_Adapters-AssociationLZ.html | 4 +- ...module-LocusZoom_Adapters-BaseAdapter.html | 4 +- ...ule-LocusZoom_Adapters-BaseApiAdapter.html | 4 +- ...dule-LocusZoom_Adapters-BaseLZAdapter.html | 1375 ++++++++++++++- ...dule-LocusZoom_Adapters-BaseUMAdapter.html | 1433 +++++++++++++++- ...dule-LocusZoom_Adapters-CredibleSetLZ.html | 2 +- ...e-LocusZoom_Adapters-GeneConstraintLZ.html | 6 +- .../api/module-LocusZoom_Adapters-GeneLZ.html | 6 +- ...dule-LocusZoom_Adapters-GwasCatalogLZ.html | 6 +- .../module-LocusZoom_Adapters-IntervalLZ.html | 2 +- .../module-LocusZoom_Adapters-LDServer.html | 4 +- .../module-LocusZoom_Adapters-PheWASLZ.html | 4 +- .../module-LocusZoom_Adapters-RecombLZ.html | 6 +- ...odule-LocusZoom_Adapters-StaticSource.html | 4 +- ...ule-LocusZoom_Adapters-TabixUrlSource.html | 2 +- ...module-LocusZoom_Adapters-UserTabixLD.html | 2 +- docs/api/module-LocusZoom_Adapters.html | 2 +- docs/api/module-LocusZoom_DataFunctions.html | 2 +- ...le-LocusZoom_DataLayers-BaseDataLayer.html | 2 +- ...LocusZoom_DataLayers-annotation_track.html | 2 +- .../api/module-LocusZoom_DataLayers-arcs.html | 2 +- ...-LocusZoom_DataLayers-category_forest.html | 2 +- ...LocusZoom_DataLayers-category_scatter.html | 2 +- .../module-LocusZoom_DataLayers-forest.html | 2 +- .../module-LocusZoom_DataLayers-genes.html | 2 +- ...ocusZoom_DataLayers-highlight_regions.html | 2 +- ...module-LocusZoom_DataLayers-intervals.html | 2 +- ...sZoom_DataLayers-intervals_enrichment.html | 2 +- ...-LocusZoom_DataLayers-orthogonal_line.html | 2 +- .../module-LocusZoom_DataLayers-scatter.html | 2 +- docs/api/module-LocusZoom_DataLayers.html | 2 +- docs/api/module-LocusZoom_Layouts.html | 2 +- docs/api/module-LocusZoom_MatchFunctions.html | 2 +- docs/api/module-LocusZoom_ScaleFunctions.html | 2 +- ...ule-LocusZoom_TransformationFunctions.html | 2 +- .../module-LocusZoom_Widgets-BaseWidget.html | 2 +- .../api/module-LocusZoom_Widgets-_Button.html | 2 +- ...ule-LocusZoom_Widgets-display_options.html | 2 +- ...module-LocusZoom_Widgets-download_png.html | 2 +- ...module-LocusZoom_Widgets-download_svg.html | 2 +- ...module-LocusZoom_Widgets-filter_field.html | 2 +- docs/api/module-LocusZoom_Widgets-menu.html | 2 +- ...ule-LocusZoom_Widgets-move_panel_down.html | 2 +- ...odule-LocusZoom_Widgets-move_panel_up.html | 2 +- ...module-LocusZoom_Widgets-region_scale.html | 2 +- ...module-LocusZoom_Widgets-remove_panel.html | 2 +- ...dule-LocusZoom_Widgets-resize_to_data.html | 2 +- .../module-LocusZoom_Widgets-set_state.html | 2 +- ...module-LocusZoom_Widgets-shift_region.html | 2 +- docs/api/module-LocusZoom_Widgets-title.html | 2 +- ...odule-LocusZoom_Widgets-toggle_legend.html | 2 +- ...LocusZoom_Widgets-toggle_split_tracks.html | 2 +- .../module-LocusZoom_Widgets-zoom_region.html | 2 +- docs/api/module-LocusZoom_Widgets.html | 2 +- docs/api/module-components_legend-Legend.html | 2 +- .../module-data_requester-DataOperation.html | 2 +- docs/api/module-ext_lz-credible-sets.html | 2 +- docs/api/module-ext_lz-dynamic-urls.html | 2 +- docs/api/module-ext_lz-forest-track.html | 2 +- .../module-ext_lz-intervals-enrichment.html | 2 +- docs/api/module-ext_lz-intervals-track.html | 2 +- docs/api/module-ext_lz-parsers.html | 2 +- docs/api/module-ext_lz-tabix-source.html | 2 +- ...ext_lz-widget-addons-covariates_model.html | 2 +- ...dule-ext_lz-widget-addons-data_layers.html | 2 +- docs/api/module-ext_lz-widget-addons.html | 2 +- .../module-registry_base-RegistryBase.html | 2 +- .../module-undercomplicate.BaseAdapter.html | 1497 +++++++++++++++++ ...module-undercomplicate.BaseUrlAdapter.html | 1410 ++++++++++++++++ docs/api/module-undercomplicate.LRUCache.html | 240 +++ docs/api/module-undercomplicate.html | 1046 ++++++++++++ docs/api/registry_adapters.js.html | 2 +- docs/api/registry_base.js.html | 2 +- docs/api/registry_data_layers.js.html | 2 +- docs/api/registry_data_ops.js.html | 4 +- docs/api/registry_layouts.js.html | 2 +- docs/api/registry_matchers.js.html | 2 +- docs/api/registry_transforms.js.html | 4 +- docs/api/registry_widgets.js.html | 2 +- docs/guides/data_retrieval.html | 29 +- esm/version.js | 2 +- index.html | 4 +- package-lock.json | 2 +- package.json | 2 +- 150 files changed, 8839 insertions(+), 295 deletions(-) create mode 100644 docs/api/LLNode.html create mode 100644 docs/api/data_undercomplicate_adapter.js.html create mode 100644 docs/api/data_undercomplicate_index.js.html create mode 100644 docs/api/data_undercomplicate_joins.js.html create mode 100644 docs/api/data_undercomplicate_lru_cache.js.html create mode 100644 docs/api/data_undercomplicate_requests.js.html create mode 100644 docs/api/data_undercomplicate_util.js.html create mode 100644 docs/api/module-undercomplicate.BaseAdapter.html create mode 100644 docs/api/module-undercomplicate.BaseUrlAdapter.html create mode 100644 docs/api/module-undercomplicate.LRUCache.html create mode 100644 docs/api/module-undercomplicate.html diff --git a/dist/ext/lz-aggregation-tests.min.js b/dist/ext/lz-aggregation-tests.min.js index e5546c05..d1e58698 100644 --- a/dist/ext/lz-aggregation-tests.min.js +++ b/dist/ext/lz-aggregation-tests.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.2 */ +/*! Locuszoom 0.14.0-beta.3 */ var LzAggregationTests;(()=>{"use strict";var e={d:(t,r)=>{for(var s in r)e.o(r,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:r[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>o});const r=raremetal;function s(e){const t=e.Adapters.get("BaseLZAdapter");e.DataFunctions.add("gene_plus_aggregation",((e,[t,r])=>{const s={};return r.groups.forEach((function(e){Object.prototype.hasOwnProperty.call(s,e.group)||(s[e.group]=[]),s[e.group].push(e.pvalue)})),t.forEach((e=>{const t=e.gene_name,r=s[t];r&&(e.aggregation_best_pvalue=Math.min.apply(null,r))})),t})),e.Adapters.add("AggregationTestSourceLZ",class extends t{constructor(e){e.prefix_namespace=!1,super(e)}_buildRequestOptions(e){const{aggregation_tests:t={}}=e,{genoset_id:r=null,genoset_build:s=null,phenoset_build:o=null,pheno:n=null,calcs:a={},masks:u=[]}=t;return t.mask_ids=u.map((e=>e.name)),e.aggregation_tests=t,e}_getURL(e){return this._url}_getCacheKey(e){const{chr:t,start:r,end:s,aggregation_tests:o}=e,{genoset_id:n=null,genoset_build:a=null,phenoset_id:u=null,pheno:l=null,mask_ids:g}=o;return JSON.stringify({chrom:t,start:r,stop:s,genotypeDataset:n,phenotypeDataset:u,phenotype:l,samples:"ALL",genomeBuild:a,masks:g})}_performRequest(e){const t=this._getURL(e),r=this._getCacheKey(e);return fetch(t,{method:"POST",body:r,headers:{"Content-Type":"application/json"}}).then((e=>{if(!e.ok)throw new Error(e.statusText);return e.text()})).then((e=>{const t="string"==typeof e?JSON.parse(e):e;if(t.error)throw new Error(t.error);return t.data}))}_annotateRecords(e,t){const{aggregation_tests:s}=t,{calcs:o=[],mask_ids:n=[],masks:a=[]}=s;if(!e.groups)return{groups:[],variants:[]};e.groups=e.groups.filter((e=>"GENE"===e.groupType));const u=r.helpers.parsePortalJSON(e);let l=u[0];const g=u[1];if(l=l.byMask(n),!o||0===Object.keys(o).length)return{variants:[],groups:[],results:[]};return new r.helpers.PortalTestRunner(l,g,o).toJSON().then((function(e){const t=a.reduce(((e,t)=>(e[t.name]=t.description,e)),{});return e.data.groups.forEach((e=>{e.mask_name=t[e.mask]})),e.data})).catch((function(e){throw console.error(e),new Error("Failed to calculate aggregation test results")}))}}),e.Adapters.add("AssocFromAggregationLZ",class extends t{_buildRequestOptions(e,t){if(!t)throw new Error("Aggregation test results must be provided");return e._agg_results=t,e}_performRequest(e){return Promise.resolve(e._agg_results.variants)}_normalizeResponse(e){const t=new RegExp("(?:chr)?(.+):(\\d+)_?(\\w+)?/?([^_]+)?_?(.*)?");return e.map((e=>{const{variant:r,altFreq:s,pvalue:o}=e,n=r.match(t),[a,u,l,g]=n;return{variant:r,chromosome:u,position:+l,ref_allele:g,ref_allele_freq:1-s,log_pvalue:-Math.log10(o)}})).sort(((e,t)=>(e=e.variant)<(t=t.variant)?-1:e>t?1:0))}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(s);const o=s;LzAggregationTests=t.default})(); //# sourceMappingURL=lz-aggregation-tests.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-credible-sets.min.js b/dist/ext/lz-credible-sets.min.js index 8819534f..dbc3b5e2 100644 --- a/dist/ext/lz-credible-sets.min.js +++ b/dist/ext/lz-credible-sets.min.js @@ -1,4 +1,4 @@ -/*! Locuszoom 0.14.0-beta.2 */ +/*! Locuszoom 0.14.0-beta.3 */ var LzCredibleSets;(()=>{var e={803:function(e){var t;t=function(){return function(e){var t={};function r(o){if(t[o])return t[o].exports;var a=t[o]={i:o,l:!1,exports:{}};return e[o].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(e,t,r){"use strict"; /** * @module stats diff --git a/dist/ext/lz-credible-sets.min.js.map b/dist/ext/lz-credible-sets.min.js.map index 47419145..88afad00 100644 --- a/dist/ext/lz-credible-sets.min.js.map +++ b/dist/ext/lz-credible-sets.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/../../../../../webpack/universalModuleDefinition","webpack://[name]/../../../../../webpack/bootstrap 069b89435249357eaca3","webpack://[name]/../../../../../src/app/stats.js","webpack://[name]/../../../../../src/app/gwas-credible-sets.js","webpack://[name]/../../../../../src/app/scoring.js","webpack://[name]/../../../../../src/app/marking.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-credible-sets.js"],"names":["factory","module","Object","prototype","hasOwnProperty","call","object","property","ninv","p","a","b","c","d","e","f","q","r","x","Math","abs","sqrt","log","rollup","scoring","stats","marking","_nlogp_to_z2","nlogp","pow","bayesFactors","nlogpvals","cap","Array","isArray","length","z2_2","map","item","capValue","max","exp","normalizeProbabilities","scores","sumValues","reduce","findCredibleSet","probs","cutoff","Number","isNaN","statsTotal","sortedStatsMap","index","sort","runningTotal","result","fill","i","value","score","markBoolean","credibleSetMembers","rescaleCredibleSet","sumMarkers","exports","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","definition","key","o","defineProperty","enumerable","get","obj","prop","install","LocusZoom","BaseUMAdapter","Adapters","add","config","super","arguments","this","_config","assign","threshold","significance_threshold","_prefix_namespace","state","credible_set_threshold","chr","start","end","join","options","assoc_data","base","_buildRequestOptions","_assoc_data","Promise","resolve","assoc_logp_name","_findPrefixedKey","some","val","posteriorProbabilities","credibleSet","credSetScaled","credSetBool","_provider_name","console","error","association_credible_set_tooltip","l","Layouts","html","closable","show","or","hide","and","association_credible_set_layer","id","namespace","data_operations","type","from","name","requires","params","fill_opacity","tooltip","match","send","receive","color","unshift","field","scale_function","parameters","field_value","then","annotation_credible_set_layer","id_field","x_axis","filters","operator","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip_positioning","annotation_credible_set","title","text","style","min_height","height","margin","top","right","bottom","left","inner_border","toolbar","axes","extent","render","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","data_layers","association_credible_set_panel","widgets","push","position","button_html","button_title","layer_name","default_config_display_name","display_name","display","point_shape","point_size","else","legend","shape","size","label","class","breaks","values","association_credible_set_plot","width","responsive_resize","min_region_scale","max_region_scale","panels","use"],"mappings":"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","file":"ext/lz-credible-sets.min.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"gwasCredibleSets\"] = factory();\n\telse\n\t\troot[\"gwasCredibleSets\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 069b89435249357eaca3","/** \n * @module stats \n * @license MIT\n * */\n\n/**\n * The inverse of the standard normal CDF. May be used to determine the Z-score for the desired quantile.\n *\n * This is an implementation of algorithm AS241:\n * https://www.jstor.org/stable/2347330\n * \n * @param {number} p The desired quantile of the standard normal distribution.\n * @returns {number}\n */\nfunction ninv(p) {\n const SPLIT1 = 0.425;\n const SPLIT2 = 5.0;\n const CONST1 = 0.180625;\n const CONST2 = 1.6;\n const a = [\n 3.3871328727963666080E0,\n 1.3314166789178437745E2,\n 1.9715909503065514427E3,\n 1.3731693765509461125E4,\n 4.5921953931549871457E4,\n 6.7265770927008700853E4,\n 3.3430575583588128105E4,\n 2.5090809287301226727E3\n ];\n\n const b = [\n 4.2313330701600911252E1,\n 6.8718700749205790830E2,\n 5.3941960214247511077E3,\n 2.1213794301586595867E4,\n 3.9307895800092710610E4,\n 2.8729085735721942674E4,\n 5.2264952788528545610E3\n ];\n\n const c = [\n 1.42343711074968357734E0,\n 4.63033784615654529590E0,\n 5.76949722146069140550E0,\n 3.64784832476320460504E0,\n 1.27045825245236838258E0,\n 2.41780725177450611770E-1,\n 2.27238449892691845833E-2,\n 7.74545014278341407640E-4\n ];\n\n const d = [\n 2.05319162663775882187E0,\n 1.67638483018380384940E0,\n 6.89767334985100004550E-1,\n 1.48103976427480074590E-1,\n 1.51986665636164571966E-2,\n 5.47593808499534494600E-4,\n 1.05075007164441684324E-9\n ];\n\n const e = [\n 6.65790464350110377720E0,\n 5.46378491116411436990E0,\n 1.78482653991729133580E0,\n 2.96560571828504891230E-1,\n 2.65321895265761230930E-2,\n 1.24266094738807843860E-3,\n 2.71155556874348757815E-5,\n 2.01033439929228813265E-7\n ];\n\n const f = [\n 5.99832206555887937690E-1,\n 1.36929880922735805310E-1,\n 1.48753612908506148525E-2,\n 7.86869131145613259100E-4,\n 1.84631831751005468180E-5,\n 1.42151175831644588870E-7,\n 2.04426310338993978564E-15\n ];\n\n const q = p - 0.5;\n let r, x;\n\n if (Math.abs(q) < SPLIT1) {\n r = CONST1 - q * q;\n return q * ((((((( a[7] * r + a[6] ) * r + a[5] ) * r + a[4] ) * r\n + a[3] ) * r + a[2] ) * r + a[1] ) * r + a[0] ) /\n ((((((( b[6] * r + b[5] ) * r + b[4] ) * r + b[3] ) * r\n + b[2] ) * r + b[1] ) * r + b[0] ) * r + 1.0 );\n }\n else {\n if (q < 0) {\n r = p\n }\n else {\n r = 1.0 - p\n }\n\n if (r > 0) {\n r = Math.sqrt(-Math.log(r));\n if (r <= SPLIT2) {\n r -= CONST2;\n x = ((((((( c[7] * r + c[6] ) * r + c[5] ) * r + c[4] ) * r\n + c[3] ) * r + c[2] ) * r + c[1] ) * r + c[0] ) /\n ((((((( d[6] * r + d[5] ) * r + d[4] ) * r + d[3] ) * r\n + d[2] ) * r + d[1] ) * r + d[0] ) * r + 1.0 );\n }\n else {\n r -= SPLIT2;\n x = ((((((( e[7] * r + e[6] ) * r + e[5] ) * r + e[4] ) * r\n + e[3] ) * r + e[2] ) * r + e[1] ) * r + e[0] ) /\n ((((((( f[6] * r + f[5] ) * r + f[4] ) * r + f[3] ) * r\n + f[2] ) * r + f[1] ) * r + f[0] ) * r + 1.0 );\n }\n }\n else {\n throw('Not implemented')\n }\n\n if (q < 0) {\n x = -x\n }\n\n return x;\n }\n}\n\n// Hack: A single global object representing the contents of the module\nconst rollup = { ninv };\n\nexport { ninv };\nexport default rollup;\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/stats.js","/** \n * Functions for calculating credible sets and Bayes factors from\n * genome-wide association study (GWAS) results. \n * @module gwas-credible-sets \n * @license MIT\n */\n\nimport stats from './stats';\nimport scoring from './scoring';\nimport marking from './marking';\n\n// HACK: Because a primary audience is targets that do not have any module system, we will expose submodules from the\n// top-level module. (by representing each sub-module as a \"rollup object\" that exposes its internal methods)\n// Then, submodules may be accessed as `window.gwasCredibleSets.stats`, etc\n\n// If you are using a real module system, please import from sub-modules directly- these global helpers are a bit of\n// a hack and may go away in the future\nexport { scoring, stats, marking };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/gwas-credible-sets.js","/** \n * @module scoring \n * @license MIT\n */\n\nimport { ninv } from './stats';\n\n\n/**\n * Convert a -log10 p-value to Z^2.\n *\n * Very large -log10 p-values (very small p-values) cannot be converted to a Z-statistic directly in the browser due to\n * limitations in javascript (64-bit floats.) These values are handled using an approximation:\n * for small p-values, Z_i^2 has a linear relationship with -log10 p-value.\n *\n * The approximation begins for -log10 p-values >= 300.\n *\n * @param nlogp\n * @return {number}\n * @private\n */\nfunction _nlogp_to_z2(nlogp) {\n const p = Math.pow(10, -nlogp);\n if (nlogp < 300) {\n // Use exact method when within the range of 64-bit floats (approx 10^-323)\n return Math.pow(ninv(p / 2), 2);\n }\n else {\n // For very small p-values, -log10(pval) and Z^2 have a linear relationship\n // This avoids issues with needing higher precision floats when doing the calculation\n // with ninv\n return (4.59884133027944 * nlogp) - 5.88085867031722\n }\n}\n\n/**\n * Calculate a Bayes factor exp(Z^2 / 2) based on p-values. If the Z-score is very large, the Bayes factors\n * are calculated in an inexact (capped) manner that makes the calculation tractable but preserves comparisons.\n * @param {Number[]} nlogpvals An array of -log10(p-value) entries\n * @param {Boolean} [cap=true] Whether to apply an inexact method. If false, some values in the return array may\n * be represented as \"Infinity\", but the Bayes factors will be directly calculated wherever possible.\n * @return {Number[]} An array of exp(Z^2 / 2) statistics\n */\nfunction bayesFactors(nlogpvals, cap=true) {\n if (!Array.isArray(nlogpvals) || ! nlogpvals.length) {\n throw 'Must provide a non-empty array of pvalues';\n }\n\n // 1. Convert the pvalues to Z^2 / 2 values. Divide by 2 before applying the cap, because it means fewer values will\n // need to be truncated. This does affect some of the raw bayes factors that are returned (when a cap is needed),\n // but the resulting credible set contents / posterior probabilities are unchanged.\n let z2_2 = nlogpvals.map(item => _nlogp_to_z2(item) / 2);\n\n // 2. Calculate bayes factor, using a truncation approach that prevents overrunning the max float64 value\n // (when Z^2 / 2 > 709 or so). As safeguard, we could (but currently don't) check that exp(Z^2 / 2) is not larger\n // than infinity.\n if (cap) {\n const capValue = Math.max(...z2_2) - 708; // The real cap is ~709; this should prevent any value from exceeding it\n if (capValue > 0) {\n z2_2 = z2_2.map(item => (item - capValue));\n }\n }\n return z2_2.map(item => Math.exp(item));\n}\n\n/**\n * Normalize so that sum of all elements = 1.0. This method must be applied to bayes factors before calculating any\n * credible set.\n *\n * @param {Number[]} scores An array of probability scores for all elements in the range\n * @returns {Number[]} Posterior probabilities\n */\nfunction normalizeProbabilities(scores) {\n const sumValues = scores.reduce((a, b) => a + b, 0);\n return scores.map(item => item / sumValues);\n}\n\nconst rollup = { bayesFactors, normalizeProbabilities };\nexport default rollup;\nexport { bayesFactors, normalizeProbabilities };\n\n// Export additional symbols for unit testing only (not part of public interface for the module)\nexport { _nlogp_to_z2 };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/scoring.js","/**\n * @module marking\n * @license MIT\n */\n\n/**\n * Given an array of probabilities, determine which elements of the array fall within the X% credible set,\n * where X is the cutoff value.\n *\n * @param {Number[]} probs Calculated probabilities used to rank the credible set. This method will normalize the\n * provided input to ensure that the values sum to 1.0.\n * @param {Number} [cutoff=0.95] Keep taking items until we have accounted for >= this fraction of the total probability.\n * For example, 0.95 would represent the 95% credible set.\n * @return {Number[]} An array with posterior probabilities (for the items in the credible set), and zero for all\n * other elements. This array is the same length as the provided probabilities array.\n */\nfunction findCredibleSet(probs, cutoff=0.95) {\n // Type checking\n if (!Array.isArray(probs) || !probs.length) {\n throw 'Probs must be a non-empty array';\n }\n if (!(typeof cutoff === 'number' ) || cutoff < 0 || cutoff > 1.0 || Number.isNaN(cutoff)) {\n throw 'Cutoff must be a number between 0 and 1';\n }\n\n const statsTotal = probs.reduce((a, b) => a + b, 0);\n if (statsTotal <= 0) {\n throw 'Sum of provided probabilities must be > 0';\n }\n\n // Sort the probabilities by largest first, while preserving a map to original item order\n const sortedStatsMap = probs\n .map((item, index) => [item, index])\n .sort((a, b) => (b[0] - a[0]));\n\n let runningTotal = 0;\n const result = new Array(sortedStatsMap.length).fill(0);\n for (let i = 0; i < sortedStatsMap.length; i++) {\n let [value, index] = sortedStatsMap[i];\n if (runningTotal < cutoff) {\n // Convert from a raw score to posterior probability by dividing the item under consideration\n // by sum of all probabilities.\n const score = value / statsTotal;\n result[index] = score;\n runningTotal += score;\n } else {\n break;\n }\n }\n return result;\n}\n\n/**\n * Given a numeric [pre-calculated credible set]{@link #findCredibleSet}, return an array of booleans where true\n * denotes membership in the credible set.\n *\n * This is a helper method used when visualizing the members of the credible set by raw membership.\n *\n * @param {Number[]} credibleSetMembers An array indicating contributions to the credible set, where non-members are\n * represented by some falsy value.\n * @return {Boolean[]} An array of booleans identifying whether or not each item is in the credible set.\n * This array is the same length as the provided credible set array.\n */\nfunction markBoolean(credibleSetMembers) {\n return credibleSetMembers.map(item => !!item);\n}\n\n/**\n * Visualization helper method for rescaling data to a predictable output range, eg when range for a color gradient\n * must be specified in advance.\n *\n * Given an array of probabilities for items in a credible set, rescale the probabilities within only the credible\n * set to their total sum.\n *\n * Example for 95% credible set: [0.92, 0.06, 0.02] -> [0.938, 0.061, 0]. The first two elements here\n * belong to the credible set, the last element does not.\n *\n * @param {Number[]} credibleSetMembers Calculated probabilities used to rank the credible set.\n * @return {Number[]} The fraction of credible set probabilities each item accounts for.\n * This array is the same length as the provided credible set.\n */\nfunction rescaleCredibleSet(credibleSetMembers) {\n const sumMarkers = credibleSetMembers.reduce((a, b) => a + b, 0);\n return credibleSetMembers.map(item => item / sumMarkers);\n}\n\nconst rollup = { findCredibleSet, markBoolean, rescaleCredibleSet };\nexport default rollup;\nexport { findCredibleSet, markBoolean, rescaleCredibleSet };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/marking.js","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Custom code used to power credible sets demonstration example. This is not part of the core LocusZoom library,\n * but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~CredibleSetLZ}\n * * {@link module:LocusZoom_Layouts~association_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_layer}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set_plot}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import credibleSets from 'locuszoom/esm/ext/lz-credible-sets';\n * LocusZoom.use(credibleSets);\n * ```\n @module\n*/\n\nimport {marking, scoring} from 'gwas-credible-sets';\n\nfunction install (LocusZoom) {\n const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter');\n\n /**\n * (**extension**) Custom data adapter that calculates the 95% credible set based on provided association data.\n * This source must be requested as the second step in a chain, after a previous step that returns fields required\n * for the calculation. (usually, it follows a request for GWAS summary statistics)\n * @alias module:LocusZoom_Adapters~CredibleSetLZ\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n class CredibleSetLZ extends BaseUMAdapter {\n /**\n * @param {Number} [config.params.threshold=0.95] The credible set threshold (eg 95%). Will continue selecting SNPs\n * until the posterior probabilities add up to at least this fraction of the total.\n * @param {Number} [config.params.significance_threshold=7.301] Do not perform a credible set calculation for this\n * region unless AT LEAST ONE SNP (as -log10p) exceeds the line of GWAS signficance. Otherwise we are declaring a\n * credible set when there is no evidence of anything being significant at all. If one snp is significant, it will\n * create a credible set for the entire region; the resulting set may include things below the line of significance.\n */\n constructor(config) {\n super(...arguments);\n // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p)\n this._config = Object.assign(\n { threshold: 0.95, significance_threshold: 7.301 },\n this._config\n );\n this._prefix_namespace = false;\n }\n\n _getCacheKey (state) {\n const threshold = state.credible_set_threshold || this._config.threshold;\n return [threshold, state.chr, state.start, state.end].join('_');\n }\n\n _buildRequestOptions(options, assoc_data) {\n const base = super._buildRequestOptions(...arguments);\n base._assoc_data = assoc_data;\n return base;\n }\n\n _performRequest(options) {\n const {_assoc_data} = options;\n if (!_assoc_data.length) {\n // No credible set can be calculated because there is no association data for this region\n return Promise.resolve([]);\n }\n\n const assoc_logp_name = this._findPrefixedKey(_assoc_data[0], 'log_pvalue');\n\n const threshold = this._config.threshold;\n\n // Calculate raw bayes factors and posterior probabilities based on information returned from the API\n const nlogpvals = _assoc_data.map((item) => item[assoc_logp_name]);\n\n if (!nlogpvals.some((val) => val >= this._config.significance_threshold)) {\n // If NO points have evidence of significance, define the credible set to be empty\n // (rather than make a credible set that we don't think is meaningful)\n return Promise.resolve(_assoc_data);\n }\n\n try {\n const scores = scoring.bayesFactors(nlogpvals);\n const posteriorProbabilities = scoring.normalizeProbabilities(scores);\n\n // Use scores to mark the credible set in various ways (depending on your visualization preferences,\n // some of these may not be needed)\n const credibleSet = marking.findCredibleSet(posteriorProbabilities, threshold);\n const credSetScaled = marking.rescaleCredibleSet(credibleSet);\n const credSetBool = marking.markBoolean(credibleSet);\n\n // Annotate each response record based on credible set membership. This has the effect of joining\n // credset results to assoc data directly within the adapter (no separate join needed)\n for (let i = 0; i < _assoc_data.length; i++) {\n _assoc_data[i][`${options._provider_name}:posterior_prob`] = posteriorProbabilities[i];\n _assoc_data[i][`${options._provider_name}:contrib_fraction`] = credSetScaled[i];\n _assoc_data[i][`${options._provider_name}:is_member`] = credSetBool[i];\n }\n } catch (e) {\n // If the calculation cannot be completed, return the data without annotation fields\n console.error(e);\n }\n return Promise.resolve(_assoc_data);\n }\n }\n\n LocusZoom.Adapters.add('CredibleSetLZ', CredibleSetLZ);\n\n // Add related layouts to the central global registry\n /**\n * (**extension**) Tooltip layout that appends credible set posterior probability to the default association tooltip (for SNPs in the credible set)\n * @alias module:LocusZoom_Layouts~association_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_tooltip = function () {\n // Extend a known tooltip with an extra row of info showing posterior probabilities\n const l = LocusZoom.Layouts.get('tooltip', 'standard_association');\n l.html += '{{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}';\n return l;\n }();\n\n LocusZoom.Layouts.add('tooltip', 'association_credible_set', association_credible_set_tooltip);\n\n /**\n * (**extension**) A tooltip layout for annotation (rug) tracks that provides information about credible set members\n * @alias module:LocusZoom_Layouts~annotation_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{assoc:variant|htmlescape}}
    '\n + 'P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    ' +\n '{{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}',\n };\n LocusZoom.Layouts.add('tooltip', 'annotation_credible_set', annotation_credible_set_tooltip);\n\n /**\n * (**extension**) A data layer layout that shows GWAS summary statistics overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n\n const association_credible_set_layer = function () {\n const base = LocusZoom.Layouts.get('data_layer', 'association_pvalues', {\n id: 'associationcredibleset',\n namespace: { 'assoc': 'assoc', 'credset': 'credset', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)', 'credset(assoc)'],\n },\n {\n type: 'left_match',\n name: 'credset_plus_ld',\n requires: ['credset', 'ld'], // The credible sets demo wasn't fully moved over to the new data operations system, and as such it is a bit weird\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n fill_opacity: 0.7,\n tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set'),\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n });\n base.color.unshift({\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#FFf000',\n },\n });\n return base;\n }();\n LocusZoom.Layouts.add('data_layer', 'association_credible_set', association_credible_set_layer);\n\n /**\n * (**extension**) A data layer layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_layer = {\n namespace: { 'assoc': 'assoc', 'credset': 'credset' },\n data_operations: [{\n type: 'fetch',\n from: ['assoc', 'credset(assoc)'],\n }],\n id: 'annotationcredibleset',\n type: 'annotation_track',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#001cee',\n },\n },\n '#00CC00',\n ],\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'credset:is_member', operator: '=', value: true },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: LocusZoom.Layouts.get('tooltip', 'annotation_credible_set'),\n tooltip_positioning: 'top',\n };\n LocusZoom.Layouts.add('data_layer', 'annotation_credible_set', annotation_credible_set_layer);\n\n /**\n * (**extension**) A panel layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set = {\n id: 'annotationcredibleset',\n title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } },\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 50, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel'),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'annotation_credible_set'),\n ],\n };\n LocusZoom.Layouts.add('panel', 'annotation_credible_set', annotation_credible_set);\n\n /**\n * (**extension**) A panel layout that shows GWAS summary statistics in a standard LocusZoom view, overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_panel = function () {\n const l = LocusZoom.Layouts.get('panel', 'association', {\n id: 'associationcrediblesets',\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'significance'),\n LocusZoom.Layouts.get('data_layer', 'recomb_rate'),\n LocusZoom.Layouts.get('data_layer', 'association_credible_set'),\n ],\n });\n // Add \"display options\" button to control how credible set coloring is overlaid on the standard association plot\n l.toolbar.widgets.push(\n {\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n layer_name: 'associationcredibleset',\n default_config_display_name: 'Linkage Disequilibrium (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: '95% credible set (boolean)', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n point_shape: 'circle',\n point_size: 40,\n color: {\n field: 'credset:is_member',\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#00CC00',\n else: '#CCCCCC',\n },\n },\n legend: [ // Tells the legend how to represent this display option\n {\n shape: 'circle',\n color: '#00CC00',\n size: 40,\n label: 'In credible set',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#CCCCCC',\n size: 40,\n label: 'Not in credible set',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n {\n // Second option. The same plot- or even the same field- can be colored in more than one way.\n display_name: '95% credible set (gradient by contribution)',\n display: {\n point_shape: 'circle',\n point_size: 40,\n color: [\n {\n field: 'credset:contrib_fraction',\n scale_function: 'if',\n parameters: {\n field_value: 0,\n then: '#777777',\n },\n },\n {\n scale_function: 'interpolate',\n field: 'credset:contrib_fraction',\n parameters: {\n breaks: [0, 1],\n values: ['#fafe87', '#9c0000'],\n },\n },\n ],\n legend: [\n {\n shape: 'circle',\n color: '#777777',\n size: 40,\n label: 'No contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#fafe87',\n size: 40,\n label: 'Some contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#9c0000',\n size: 40,\n label: 'Most contribution',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n ],\n }\n );\n return l;\n }();\n LocusZoom.Layouts.add('panel', 'association_credible_set', association_credible_set_panel);\n\n /**\n * (**extension**) A standard LocusZoom plot layout, with additional credible set information.\n * @alias module:LocusZoom_Layouts~association_credible_set_plot\n * @type plot\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_plot = {\n state: {},\n width: 800,\n height: 450,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n LocusZoom.Layouts.get('panel', 'association_credible_set'),\n LocusZoom.Layouts.get('panel', 'annotation_credible_set'),\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n LocusZoom.Layouts.add('plot', 'association_credible_set', association_credible_set_plot);\n}\n\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/../../../../../webpack/universalModuleDefinition","webpack://[name]/../../../../../webpack/bootstrap 069b89435249357eaca3","webpack://[name]/../../../../../src/app/stats.js","webpack://[name]/../../../../../src/app/gwas-credible-sets.js","webpack://[name]/../../../../../src/app/scoring.js","webpack://[name]/../../../../../src/app/marking.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-credible-sets.js"],"names":["factory","module","Object","prototype","hasOwnProperty","call","object","property","ninv","p","a","b","c","d","e","f","q","r","x","Math","abs","sqrt","log","rollup","scoring","stats","marking","_nlogp_to_z2","nlogp","pow","bayesFactors","nlogpvals","cap","Array","isArray","length","z2_2","map","item","capValue","max","exp","normalizeProbabilities","scores","sumValues","reduce","findCredibleSet","probs","cutoff","Number","isNaN","statsTotal","sortedStatsMap","index","sort","runningTotal","result","fill","i","value","score","markBoolean","credibleSetMembers","rescaleCredibleSet","sumMarkers","exports","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","definition","key","o","defineProperty","enumerable","get","obj","prop","install","LocusZoom","BaseUMAdapter","Adapters","add","config","super","arguments","this","_config","assign","threshold","significance_threshold","_prefix_namespace","state","credible_set_threshold","chr","start","end","join","options","assoc_data","base","_buildRequestOptions","_assoc_data","Promise","resolve","assoc_logp_name","_findPrefixedKey","some","val","posteriorProbabilities","credibleSet","credSetScaled","credSetBool","_provider_name","console","error","association_credible_set_tooltip","l","Layouts","html","closable","show","or","hide","and","association_credible_set_layer","id","namespace","data_operations","type","from","name","requires","params","fill_opacity","tooltip","match","send","receive","color","unshift","field","scale_function","parameters","field_value","then","annotation_credible_set_layer","id_field","x_axis","filters","operator","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip_positioning","annotation_credible_set","title","text","style","min_height","height","margin","top","right","bottom","left","inner_border","toolbar","axes","extent","render","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","data_layers","association_credible_set_panel","widgets","push","position","button_html","button_title","layer_name","default_config_display_name","display_name","display","point_shape","point_size","else","legend","shape","size","label","class","breaks","values","association_credible_set_plot","width","responsive_resize","min_region_scale","max_region_scale","panels","use"],"mappings":"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","file":"ext/lz-credible-sets.min.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"gwasCredibleSets\"] = factory();\n\telse\n\t\troot[\"gwasCredibleSets\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 069b89435249357eaca3","/** \n * @module stats \n * @license MIT\n * */\n\n/**\n * The inverse of the standard normal CDF. May be used to determine the Z-score for the desired quantile.\n *\n * This is an implementation of algorithm AS241:\n * https://www.jstor.org/stable/2347330\n * \n * @param {number} p The desired quantile of the standard normal distribution.\n * @returns {number}\n */\nfunction ninv(p) {\n const SPLIT1 = 0.425;\n const SPLIT2 = 5.0;\n const CONST1 = 0.180625;\n const CONST2 = 1.6;\n const a = [\n 3.3871328727963666080E0,\n 1.3314166789178437745E2,\n 1.9715909503065514427E3,\n 1.3731693765509461125E4,\n 4.5921953931549871457E4,\n 6.7265770927008700853E4,\n 3.3430575583588128105E4,\n 2.5090809287301226727E3\n ];\n\n const b = [\n 4.2313330701600911252E1,\n 6.8718700749205790830E2,\n 5.3941960214247511077E3,\n 2.1213794301586595867E4,\n 3.9307895800092710610E4,\n 2.8729085735721942674E4,\n 5.2264952788528545610E3\n ];\n\n const c = [\n 1.42343711074968357734E0,\n 4.63033784615654529590E0,\n 5.76949722146069140550E0,\n 3.64784832476320460504E0,\n 1.27045825245236838258E0,\n 2.41780725177450611770E-1,\n 2.27238449892691845833E-2,\n 7.74545014278341407640E-4\n ];\n\n const d = [\n 2.05319162663775882187E0,\n 1.67638483018380384940E0,\n 6.89767334985100004550E-1,\n 1.48103976427480074590E-1,\n 1.51986665636164571966E-2,\n 5.47593808499534494600E-4,\n 1.05075007164441684324E-9\n ];\n\n const e = [\n 6.65790464350110377720E0,\n 5.46378491116411436990E0,\n 1.78482653991729133580E0,\n 2.96560571828504891230E-1,\n 2.65321895265761230930E-2,\n 1.24266094738807843860E-3,\n 2.71155556874348757815E-5,\n 2.01033439929228813265E-7\n ];\n\n const f = [\n 5.99832206555887937690E-1,\n 1.36929880922735805310E-1,\n 1.48753612908506148525E-2,\n 7.86869131145613259100E-4,\n 1.84631831751005468180E-5,\n 1.42151175831644588870E-7,\n 2.04426310338993978564E-15\n ];\n\n const q = p - 0.5;\n let r, x;\n\n if (Math.abs(q) < SPLIT1) {\n r = CONST1 - q * q;\n return q * ((((((( a[7] * r + a[6] ) * r + a[5] ) * r + a[4] ) * r\n + a[3] ) * r + a[2] ) * r + a[1] ) * r + a[0] ) /\n ((((((( b[6] * r + b[5] ) * r + b[4] ) * r + b[3] ) * r\n + b[2] ) * r + b[1] ) * r + b[0] ) * r + 1.0 );\n }\n else {\n if (q < 0) {\n r = p\n }\n else {\n r = 1.0 - p\n }\n\n if (r > 0) {\n r = Math.sqrt(-Math.log(r));\n if (r <= SPLIT2) {\n r -= CONST2;\n x = ((((((( c[7] * r + c[6] ) * r + c[5] ) * r + c[4] ) * r\n + c[3] ) * r + c[2] ) * r + c[1] ) * r + c[0] ) /\n ((((((( d[6] * r + d[5] ) * r + d[4] ) * r + d[3] ) * r\n + d[2] ) * r + d[1] ) * r + d[0] ) * r + 1.0 );\n }\n else {\n r -= SPLIT2;\n x = ((((((( e[7] * r + e[6] ) * r + e[5] ) * r + e[4] ) * r\n + e[3] ) * r + e[2] ) * r + e[1] ) * r + e[0] ) /\n ((((((( f[6] * r + f[5] ) * r + f[4] ) * r + f[3] ) * r\n + f[2] ) * r + f[1] ) * r + f[0] ) * r + 1.0 );\n }\n }\n else {\n throw('Not implemented')\n }\n\n if (q < 0) {\n x = -x\n }\n\n return x;\n }\n}\n\n// Hack: A single global object representing the contents of the module\nconst rollup = { ninv };\n\nexport { ninv };\nexport default rollup;\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/stats.js","/** \n * Functions for calculating credible sets and Bayes factors from\n * genome-wide association study (GWAS) results. \n * @module gwas-credible-sets \n * @license MIT\n */\n\nimport stats from './stats';\nimport scoring from './scoring';\nimport marking from './marking';\n\n// HACK: Because a primary audience is targets that do not have any module system, we will expose submodules from the\n// top-level module. (by representing each sub-module as a \"rollup object\" that exposes its internal methods)\n// Then, submodules may be accessed as `window.gwasCredibleSets.stats`, etc\n\n// If you are using a real module system, please import from sub-modules directly- these global helpers are a bit of\n// a hack and may go away in the future\nexport { scoring, stats, marking };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/gwas-credible-sets.js","/** \n * @module scoring \n * @license MIT\n */\n\nimport { ninv } from './stats';\n\n\n/**\n * Convert a -log10 p-value to Z^2.\n *\n * Very large -log10 p-values (very small p-values) cannot be converted to a Z-statistic directly in the browser due to\n * limitations in javascript (64-bit floats.) These values are handled using an approximation:\n * for small p-values, Z_i^2 has a linear relationship with -log10 p-value.\n *\n * The approximation begins for -log10 p-values >= 300.\n *\n * @param nlogp\n * @return {number}\n * @private\n */\nfunction _nlogp_to_z2(nlogp) {\n const p = Math.pow(10, -nlogp);\n if (nlogp < 300) {\n // Use exact method when within the range of 64-bit floats (approx 10^-323)\n return Math.pow(ninv(p / 2), 2);\n }\n else {\n // For very small p-values, -log10(pval) and Z^2 have a linear relationship\n // This avoids issues with needing higher precision floats when doing the calculation\n // with ninv\n return (4.59884133027944 * nlogp) - 5.88085867031722\n }\n}\n\n/**\n * Calculate a Bayes factor exp(Z^2 / 2) based on p-values. If the Z-score is very large, the Bayes factors\n * are calculated in an inexact (capped) manner that makes the calculation tractable but preserves comparisons.\n * @param {Number[]} nlogpvals An array of -log10(p-value) entries\n * @param {Boolean} [cap=true] Whether to apply an inexact method. If false, some values in the return array may\n * be represented as \"Infinity\", but the Bayes factors will be directly calculated wherever possible.\n * @return {Number[]} An array of exp(Z^2 / 2) statistics\n */\nfunction bayesFactors(nlogpvals, cap=true) {\n if (!Array.isArray(nlogpvals) || ! nlogpvals.length) {\n throw 'Must provide a non-empty array of pvalues';\n }\n\n // 1. Convert the pvalues to Z^2 / 2 values. Divide by 2 before applying the cap, because it means fewer values will\n // need to be truncated. This does affect some of the raw bayes factors that are returned (when a cap is needed),\n // but the resulting credible set contents / posterior probabilities are unchanged.\n let z2_2 = nlogpvals.map(item => _nlogp_to_z2(item) / 2);\n\n // 2. Calculate bayes factor, using a truncation approach that prevents overrunning the max float64 value\n // (when Z^2 / 2 > 709 or so). As safeguard, we could (but currently don't) check that exp(Z^2 / 2) is not larger\n // than infinity.\n if (cap) {\n const capValue = Math.max(...z2_2) - 708; // The real cap is ~709; this should prevent any value from exceeding it\n if (capValue > 0) {\n z2_2 = z2_2.map(item => (item - capValue));\n }\n }\n return z2_2.map(item => Math.exp(item));\n}\n\n/**\n * Normalize so that sum of all elements = 1.0. This method must be applied to bayes factors before calculating any\n * credible set.\n *\n * @param {Number[]} scores An array of probability scores for all elements in the range\n * @returns {Number[]} Posterior probabilities\n */\nfunction normalizeProbabilities(scores) {\n const sumValues = scores.reduce((a, b) => a + b, 0);\n return scores.map(item => item / sumValues);\n}\n\nconst rollup = { bayesFactors, normalizeProbabilities };\nexport default rollup;\nexport { bayesFactors, normalizeProbabilities };\n\n// Export additional symbols for unit testing only (not part of public interface for the module)\nexport { _nlogp_to_z2 };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/scoring.js","/**\n * @module marking\n * @license MIT\n */\n\n/**\n * Given an array of probabilities, determine which elements of the array fall within the X% credible set,\n * where X is the cutoff value.\n *\n * @param {Number[]} probs Calculated probabilities used to rank the credible set. This method will normalize the\n * provided input to ensure that the values sum to 1.0.\n * @param {Number} [cutoff=0.95] Keep taking items until we have accounted for >= this fraction of the total probability.\n * For example, 0.95 would represent the 95% credible set.\n * @return {Number[]} An array with posterior probabilities (for the items in the credible set), and zero for all\n * other elements. This array is the same length as the provided probabilities array.\n */\nfunction findCredibleSet(probs, cutoff=0.95) {\n // Type checking\n if (!Array.isArray(probs) || !probs.length) {\n throw 'Probs must be a non-empty array';\n }\n if (!(typeof cutoff === 'number' ) || cutoff < 0 || cutoff > 1.0 || Number.isNaN(cutoff)) {\n throw 'Cutoff must be a number between 0 and 1';\n }\n\n const statsTotal = probs.reduce((a, b) => a + b, 0);\n if (statsTotal <= 0) {\n throw 'Sum of provided probabilities must be > 0';\n }\n\n // Sort the probabilities by largest first, while preserving a map to original item order\n const sortedStatsMap = probs\n .map((item, index) => [item, index])\n .sort((a, b) => (b[0] - a[0]));\n\n let runningTotal = 0;\n const result = new Array(sortedStatsMap.length).fill(0);\n for (let i = 0; i < sortedStatsMap.length; i++) {\n let [value, index] = sortedStatsMap[i];\n if (runningTotal < cutoff) {\n // Convert from a raw score to posterior probability by dividing the item under consideration\n // by sum of all probabilities.\n const score = value / statsTotal;\n result[index] = score;\n runningTotal += score;\n } else {\n break;\n }\n }\n return result;\n}\n\n/**\n * Given a numeric [pre-calculated credible set]{@link #findCredibleSet}, return an array of booleans where true\n * denotes membership in the credible set.\n *\n * This is a helper method used when visualizing the members of the credible set by raw membership.\n *\n * @param {Number[]} credibleSetMembers An array indicating contributions to the credible set, where non-members are\n * represented by some falsy value.\n * @return {Boolean[]} An array of booleans identifying whether or not each item is in the credible set.\n * This array is the same length as the provided credible set array.\n */\nfunction markBoolean(credibleSetMembers) {\n return credibleSetMembers.map(item => !!item);\n}\n\n/**\n * Visualization helper method for rescaling data to a predictable output range, eg when range for a color gradient\n * must be specified in advance.\n *\n * Given an array of probabilities for items in a credible set, rescale the probabilities within only the credible\n * set to their total sum.\n *\n * Example for 95% credible set: [0.92, 0.06, 0.02] -> [0.938, 0.061, 0]. The first two elements here\n * belong to the credible set, the last element does not.\n *\n * @param {Number[]} credibleSetMembers Calculated probabilities used to rank the credible set.\n * @return {Number[]} The fraction of credible set probabilities each item accounts for.\n * This array is the same length as the provided credible set.\n */\nfunction rescaleCredibleSet(credibleSetMembers) {\n const sumMarkers = credibleSetMembers.reduce((a, b) => a + b, 0);\n return credibleSetMembers.map(item => item / sumMarkers);\n}\n\nconst rollup = { findCredibleSet, markBoolean, rescaleCredibleSet };\nexport default rollup;\nexport { findCredibleSet, markBoolean, rescaleCredibleSet };\n\n\n\n// WEBPACK FOOTER //\n// ./src/app/marking.js","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Custom code used to power credible sets demonstration example. This is not part of the core LocusZoom library,\n * but can be included as a standalone file.\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~CredibleSetLZ}\n * * {@link module:LocusZoom_Layouts~association_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_tooltip}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set_layer}\n * * {@link module:LocusZoom_Layouts~annotation_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set}\n * * {@link module:LocusZoom_Layouts~association_credible_set_plot}\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```\n * import LocusZoom from 'locuszoom';\n * import credibleSets from 'locuszoom/esm/ext/lz-credible-sets';\n * LocusZoom.use(credibleSets);\n * ```\n @module\n*/\n\nimport {marking, scoring} from 'gwas-credible-sets';\n\nfunction install (LocusZoom) {\n const BaseUMAdapter = LocusZoom.Adapters.get('BaseUMAdapter');\n\n /**\n * (**extension**) Custom data adapter that calculates the 95% credible set based on provided association data.\n * This source must be requested as the second step in a chain, after a previous step that returns fields required\n * for the calculation. (usually, it follows a request for GWAS summary statistics)\n * @alias module:LocusZoom_Adapters~CredibleSetLZ\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n class CredibleSetLZ extends BaseUMAdapter {\n /**\n * @param {Number} [config.params.threshold=0.95] The credible set threshold (eg 95%). Will continue selecting SNPs\n * until the posterior probabilities add up to at least this fraction of the total.\n * @param {Number} [config.params.significance_threshold=7.301] Do not perform a credible set calculation for this\n * region unless AT LEAST ONE SNP (as -log10p) exceeds the line of GWAS signficance. Otherwise we are declaring a\n * credible set when there is no evidence of anything being significant at all. If one snp is significant, it will\n * create a credible set for the entire region; the resulting set may include things below the line of significance.\n */\n constructor(config) {\n super(...arguments);\n // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p)\n this._config = Object.assign(\n { threshold: 0.95, significance_threshold: 7.301 },\n this._config,\n );\n this._prefix_namespace = false;\n }\n\n _getCacheKey (state) {\n const threshold = state.credible_set_threshold || this._config.threshold;\n return [threshold, state.chr, state.start, state.end].join('_');\n }\n\n _buildRequestOptions(options, assoc_data) {\n const base = super._buildRequestOptions(...arguments);\n base._assoc_data = assoc_data;\n return base;\n }\n\n _performRequest(options) {\n const {_assoc_data} = options;\n if (!_assoc_data.length) {\n // No credible set can be calculated because there is no association data for this region\n return Promise.resolve([]);\n }\n\n const assoc_logp_name = this._findPrefixedKey(_assoc_data[0], 'log_pvalue');\n\n const threshold = this._config.threshold;\n\n // Calculate raw bayes factors and posterior probabilities based on information returned from the API\n const nlogpvals = _assoc_data.map((item) => item[assoc_logp_name]);\n\n if (!nlogpvals.some((val) => val >= this._config.significance_threshold)) {\n // If NO points have evidence of significance, define the credible set to be empty\n // (rather than make a credible set that we don't think is meaningful)\n return Promise.resolve(_assoc_data);\n }\n\n try {\n const scores = scoring.bayesFactors(nlogpvals);\n const posteriorProbabilities = scoring.normalizeProbabilities(scores);\n\n // Use scores to mark the credible set in various ways (depending on your visualization preferences,\n // some of these may not be needed)\n const credibleSet = marking.findCredibleSet(posteriorProbabilities, threshold);\n const credSetScaled = marking.rescaleCredibleSet(credibleSet);\n const credSetBool = marking.markBoolean(credibleSet);\n\n // Annotate each response record based on credible set membership. This has the effect of joining\n // credset results to assoc data directly within the adapter (no separate join needed)\n for (let i = 0; i < _assoc_data.length; i++) {\n _assoc_data[i][`${options._provider_name}:posterior_prob`] = posteriorProbabilities[i];\n _assoc_data[i][`${options._provider_name}:contrib_fraction`] = credSetScaled[i];\n _assoc_data[i][`${options._provider_name}:is_member`] = credSetBool[i];\n }\n } catch (e) {\n // If the calculation cannot be completed, return the data without annotation fields\n console.error(e);\n }\n return Promise.resolve(_assoc_data);\n }\n }\n\n LocusZoom.Adapters.add('CredibleSetLZ', CredibleSetLZ);\n\n // Add related layouts to the central global registry\n /**\n * (**extension**) Tooltip layout that appends credible set posterior probability to the default association tooltip (for SNPs in the credible set)\n * @alias module:LocusZoom_Layouts~association_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_tooltip = function () {\n // Extend a known tooltip with an extra row of info showing posterior probabilities\n const l = LocusZoom.Layouts.get('tooltip', 'standard_association');\n l.html += '{{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}';\n return l;\n }();\n\n LocusZoom.Layouts.add('tooltip', 'association_credible_set', association_credible_set_tooltip);\n\n /**\n * (**extension**) A tooltip layout for annotation (rug) tracks that provides information about credible set members\n * @alias module:LocusZoom_Layouts~annotation_credible_set_tooltip\n * @type tooltip\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{assoc:variant|htmlescape}}
    '\n + 'P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    ' +\n '{{#if credset:posterior_prob}}
    Posterior probability: {{credset:posterior_prob|scinotation|htmlescape}}{{/if}}',\n };\n LocusZoom.Layouts.add('tooltip', 'annotation_credible_set', annotation_credible_set_tooltip);\n\n /**\n * (**extension**) A data layer layout that shows GWAS summary statistics overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n\n const association_credible_set_layer = function () {\n const base = LocusZoom.Layouts.get('data_layer', 'association_pvalues', {\n id: 'associationcredibleset',\n namespace: { 'assoc': 'assoc', 'credset': 'credset', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)', 'credset(assoc)'],\n },\n {\n type: 'left_match',\n name: 'credset_plus_ld',\n requires: ['credset', 'ld'], // The credible sets demo wasn't fully moved over to the new data operations system, and as such it is a bit weird\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n fill_opacity: 0.7,\n tooltip: LocusZoom.Layouts.get('tooltip', 'association_credible_set'),\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n });\n base.color.unshift({\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#FFf000',\n },\n });\n return base;\n }();\n LocusZoom.Layouts.add('data_layer', 'association_credible_set', association_credible_set_layer);\n\n /**\n * (**extension**) A data layer layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set_layer\n * @type data_layer\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set_layer = {\n namespace: { 'assoc': 'assoc', 'credset': 'credset' },\n data_operations: [{\n type: 'fetch',\n from: ['assoc', 'credset(assoc)'],\n }],\n id: 'annotationcredibleset',\n type: 'annotation_track',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#001cee',\n },\n },\n '#00CC00',\n ],\n match: { send: 'assoc:variant', receive: 'assoc:variant' },\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'credset:is_member', operator: '=', value: true },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n onshiftclick: [\n { action: 'toggle', status: 'selected' },\n ],\n },\n tooltip: LocusZoom.Layouts.get('tooltip', 'annotation_credible_set'),\n tooltip_positioning: 'top',\n };\n LocusZoom.Layouts.add('data_layer', 'annotation_credible_set', annotation_credible_set_layer);\n\n /**\n * (**extension**) A panel layout that shows a vertical mark whenever a SNP is a member of the credible set\n * @alias module:LocusZoom_Layouts~annotation_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const annotation_credible_set = {\n id: 'annotationcredibleset',\n title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } },\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 50, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel'),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'annotation_credible_set'),\n ],\n };\n LocusZoom.Layouts.add('panel', 'annotation_credible_set', annotation_credible_set);\n\n /**\n * (**extension**) A panel layout that shows GWAS summary statistics in a standard LocusZoom view, overlaid with credible set membership information\n * @alias module:LocusZoom_Layouts~association_credible_set\n * @type panel\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_panel = function () {\n const l = LocusZoom.Layouts.get('panel', 'association', {\n id: 'associationcrediblesets',\n data_layers: [\n LocusZoom.Layouts.get('data_layer', 'significance'),\n LocusZoom.Layouts.get('data_layer', 'recomb_rate'),\n LocusZoom.Layouts.get('data_layer', 'association_credible_set'),\n ],\n });\n // Add \"display options\" button to control how credible set coloring is overlaid on the standard association plot\n l.toolbar.widgets.push(\n {\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n layer_name: 'associationcredibleset',\n default_config_display_name: 'Linkage Disequilibrium (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: '95% credible set (boolean)', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n point_shape: 'circle',\n point_size: 40,\n color: {\n field: 'credset:is_member',\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#00CC00',\n else: '#CCCCCC',\n },\n },\n legend: [ // Tells the legend how to represent this display option\n {\n shape: 'circle',\n color: '#00CC00',\n size: 40,\n label: 'In credible set',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#CCCCCC',\n size: 40,\n label: 'Not in credible set',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n {\n // Second option. The same plot- or even the same field- can be colored in more than one way.\n display_name: '95% credible set (gradient by contribution)',\n display: {\n point_shape: 'circle',\n point_size: 40,\n color: [\n {\n field: 'credset:contrib_fraction',\n scale_function: 'if',\n parameters: {\n field_value: 0,\n then: '#777777',\n },\n },\n {\n scale_function: 'interpolate',\n field: 'credset:contrib_fraction',\n parameters: {\n breaks: [0, 1],\n values: ['#fafe87', '#9c0000'],\n },\n },\n ],\n legend: [\n {\n shape: 'circle',\n color: '#777777',\n size: 40,\n label: 'No contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#fafe87',\n size: 40,\n label: 'Some contribution',\n class: 'lz-data_layer-scatter',\n },\n {\n shape: 'circle',\n color: '#9c0000',\n size: 40,\n label: 'Most contribution',\n class: 'lz-data_layer-scatter',\n },\n ],\n },\n },\n ],\n },\n );\n return l;\n }();\n LocusZoom.Layouts.add('panel', 'association_credible_set', association_credible_set_panel);\n\n /**\n * (**extension**) A standard LocusZoom plot layout, with additional credible set information.\n * @alias module:LocusZoom_Layouts~association_credible_set_plot\n * @type plot\n * @see {@link module:ext/lz-credible-sets} for required extension and installation instructions\n */\n const association_credible_set_plot = {\n state: {},\n width: 800,\n height: 450,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association'),\n panels: [\n LocusZoom.Layouts.get('panel', 'association_credible_set'),\n LocusZoom.Layouts.get('panel', 'annotation_credible_set'),\n LocusZoom.Layouts.get('panel', 'genes'),\n ],\n };\n LocusZoom.Layouts.add('plot', 'association_credible_set', association_credible_set_plot);\n}\n\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-dynamic-urls.min.js b/dist/ext/lz-dynamic-urls.min.js index 0af428b9..073526e1 100644 --- a/dist/ext/lz-dynamic-urls.min.js +++ b/dist/ext/lz-dynamic-urls.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.2 */ +/*! Locuszoom 0.14.0-beta.3 */ var LzDynamicUrls;(()=>{"use strict";var t={d:(n,e)=>{for(var o in e)t.o(e,o)&&!t.o(n,o)&&Object.defineProperty(n,o,{enumerable:!0,get:e[o]})},o:(t,n)=>Object.prototype.hasOwnProperty.call(t,n)},n={};function e(t){const n={};if(t){const e=("?"===t[0]?t.substr(1):t).split("&");for(let t=0;ta});const a={paramsFromUrl:s,extractValues:o,plotUpdatesUrl:function(t,n,o){o=o||r;const c=function(c){const r=e(window.location.search),s=o(t,n,c),a=Object.assign({},r,s);if(Object.keys(a).some((function(t){return r[t]!=a[t]}))){const t=(i=a,`?${Object.keys(i).map((function(t){return`${encodeURIComponent(t)}=${encodeURIComponent(i[t])}`})).join("&")}`);Object.keys(r).length?history.pushState({},document.title,t):history.replaceState({},document.title,t)}var i};return t.on("state_changed",c),c},plotWatchesUrl:function(t,n,e){e=e||c;const o=function(o){const c=s(n);e(t,c)};return window.addEventListener("popstate",o),t.trackExternalListener(window,"popstate",o),o}};LzDynamicUrls=n.default})(); //# sourceMappingURL=lz-dynamic-urls.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-forest-track.min.js b/dist/ext/lz-forest-track.min.js index a1535119..0ed8b51a 100644 --- a/dist/ext/lz-forest-track.min.js +++ b/dist/ext/lz-forest-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.2 */ +/*! Locuszoom 0.14.0-beta.3 */ var LzForestTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var i in a)t.o(a,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:a[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>s});const a=d3;function i(t){const e=t.DataLayers.get("BaseDataLayer"),i={point_size:40,point_shape:"square",color:"#888888",fill_opacity:1,y_axis:{axis:2},id_field:"id",confidence_intervals:{start_field:"ci_start",end_field:"ci_end"}};class s extends e{constructor(e){e=t.Layouts.merge(e,i),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),a=`y${this.layout.y_axis.axis}_scale`,i=this.parent[a](t.data[this.layout.y_axis.field]),s=this.resolveScalableParameter(this.layout.point_size,t.data),r=Math.sqrt(s/Math.PI);return{x_min:e-r,x_max:e+r,y_min:i-r,y_max:i+r}}render(){const t=this._applyFilters(),e=`y${this.layout.y_axis.axis}_scale`;if(this.layout.confidence_intervals&&this.layout.confidence_intervals.start_field&&this.layout.confidence_intervals.end_field){const a=this.svg.group.selectAll("rect.lz-data_layer-forest.lz-data_layer-forest-ci").data(t,(t=>t[this.layout.id_field])),i=t=>{let a=this.parent.x_scale(t[this.layout.confidence_intervals.start_field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`},s=t=>{const{start_field:e,end_field:a}=this.layout.confidence_intervals,i=this.parent.x_scale,s=i(t[a])-i(t[e]);return Math.max(s,1)},r=1;a.enter().append("rect").attr("class","lz-data_layer-forest lz-data_layer-forest-ci").attr("id",(t=>`${this.getElementId(t)}_ci`)).attr("transform",`translate(0, ${isNaN(this.parent.layout.height)?0:this.parent.layout.height})`).merge(a).attr("transform",i).attr("width",s).attr("height",r),a.exit().remove()}const i=this.svg.group.selectAll("path.lz-data_layer-forest.lz-data_layer-forest-point").data(t,(t=>t[this.layout.id_field])),s=isNaN(this.parent.layout.height)?0:this.parent.layout.height,r=a.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>{const i=this.resolveScalableParameter(this.layout.point_shape,t,e),s=`symbol${i.charAt(0).toUpperCase()+i.slice(1)}`;return a[s]||null}));i.enter().append("path").attr("class","lz-data_layer-forest lz-data_layer-forest-point").attr("id",(t=>this.getElementId(t))).attr("transform",`translate(0, ${s})`).merge(i).attr("transform",(t=>{let a=this.parent.x_scale(t[this.layout.x_axis.field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`})).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>{this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}}t.DataLayers.add("forest",s),t.DataLayers.add("category_forest",class extends s{_getDataExtent(t,e){const{confidence_intervals:i}=this.layout;if(i&&i.start_field&&i.end_field){const e=t=>+t[i.start_field],s=t=>+t[i.end_field];return[a.min(t,e),a.max(t,s)]}return super._getDataExtent(t,e)}getTicks(t,e){if(!["x","y1","y2"].includes(t))throw new Error(`Invalid dimension identifier ${t}`);if(t===`y${this.layout.y_axis.axis}`){const t=this.layout.y_axis.category_field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify category_field`);return this.data.map(((e,a)=>({y:a+1,text:e[t]})))}return[]}applyCustomDataMethods(){const t=this.layout.y_axis.field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify yaxis.field`);return this.data=this.data.map(((e,a)=>(e[t]=a+1,e))),this.layout.y_axis.floor=0,this.layout.y_axis.ceiling=this.data.length+1,this}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(i);const s=i;LzForestTrack=e.default})(); //# sourceMappingURL=lz-forest-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-enrichment.min.js b/dist/ext/lz-intervals-enrichment.min.js index 796ea5fe..bd4b7306 100644 --- a/dist/ext/lz-intervals-enrichment.min.js +++ b/dist/ext/lz-intervals-enrichment.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.2 */ +/*! Locuszoom 0.14.0-beta.3 */ var LzIntervalsEnrichment;(()=>{"use strict";var e={d:(t,a)=>{for(var i in a)e.o(a,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:a[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>n});const a=Symbol.for("lzXCS"),i=Symbol.for("lzYCS"),s=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function r(e){const t={start_field:"start",end_field:"end",track_height:10,track_vertical_spacing:3,bounding_box_padding:2,color:"#B8B8B8",fill_opacity:.5,tooltip_positioning:"vertical"},r=e.DataLayers.get("BaseDataLayer");const n={namespace:{intervals:"intervals"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Tissue: {{intervals:tissueId|htmlescape}}
    \n Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
    \n -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
    \n Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}"},o={id:"intervals_enrichment",type:"intervals_enrichment",tag:"intervals_enrichment",namespace:{intervals:"intervals"},match:{send:"intervals:tissueId"},id_field:"intervals:start",start_field:"intervals:start",end_field:"intervals:end",filters:[{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],y_axis:{axis:1,field:"intervals:fold",floor:0,upper_buffer:.1,min_extent:[0,10]},fill_opacity:.5,color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:n},c={id:"interval_matches",type:"highlight_regions",namespace:{intervals:"intervals"},match:{receive:"intervals:tissueId"},start_field:"intervals:start",end_field:"intervals:end",merge_field:"intervals:tissueId",filters:[{field:"lz_is_match",operator:"=",value:!0},{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],fill_opacity:.1},d={id:"intervals_enrichment",tag:"intervals_enrichment",min_height:250,height:250,margin:{top:35,right:50,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:34,tick_format:"region",extent:"state"},y1:{label:"enrichment (n-fold)",label_offset:40}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[o]},h={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:e.Layouts.get("toolbar","standard_association"),panels:[function(){const t=e.Layouts.get("panel","association");return t.data_layers.unshift(c),t}(),d,e.Layouts.get("panel","genes")]};e.DataLayers.add("intervals_enrichment",class extends r{constructor(a){e.Layouts.merge(a,t),super(...arguments)}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}render(){const e=this._applyFilters(this.data),{start_field:t,end_field:r,bounding_box_padding:n,track_height:o}=this.layout,c=this.layout.y_axis.field,d=`y${this.layout.y_axis.axis}_scale`,{x_scale:h,[d]:f}=this.parent;e.forEach((e=>{e[a]=h(e[t]),e[s]=h(e[r]),e[i]=f(e[c])-this.getTrackHeight()/2+n,e[l]=e[i]+o})),e.sort(((e,t)=>{const i=e[s]-e[a];return t[s]-t[a]-i}));const _=this.svg.group.selectAll("rect").data(e);_.enter().append("rect").merge(_).attr("id",(e=>this.getElementId(e))).attr("x",(e=>e[a])).attr("y",(e=>e[i])).attr("width",(e=>e[s]-e[a])).attr("height",this.layout.track_height).attr("fill",((e,t)=>this.resolveScalableParameter(this.layout.color,e,t))).attr("fill-opacity",((e,t)=>this.resolveScalableParameter(this.layout.fill_opacity,e,t))),_.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this))}_getTooltipPosition(e){return{x_min:e.data[a],x_max:e.data[s],y_min:e.data[i],y_max:e.data[l]}}}),e.Layouts.add("tooltip","intervals_enrichment",n),e.Layouts.add("data_layer","intervals_enrichment",o),e.Layouts.add("panel","intervals_enrichment",d),e.Layouts.add("plot","intervals_association_enrichment",h)}"undefined"!=typeof LocusZoom&&LocusZoom.use(r);const n=r;LzIntervalsEnrichment=t.default})(); //# sourceMappingURL=lz-intervals-enrichment.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-track.min.js b/dist/ext/lz-intervals-track.min.js index 0f6a4fdb..fb4afc9a 100644 --- a/dist/ext/lz-intervals-track.min.js +++ b/dist/ext/lz-intervals-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.2 */ +/*! Locuszoom 0.14.0-beta.3 */ var LzIntervalsTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var r in a)t.o(a,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>o});const a=d3,r=Symbol.for("lzXCS"),s=Symbol.for("lzYCS"),i=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function n(t){const e=t.Adapters.get("BaseUMAdapter"),n=t.Widgets.get("_Button"),o=t.Widgets.get("BaseWidget");function c(t,e){return e?`rgb(${e})`:null}const d={start_field:"start",end_field:"end",track_label_field:"state_name",track_split_field:"state_id",track_split_order:"DESC",track_split_legend_to_y_axis:2,split_tracks:!0,track_height:15,track_vertical_spacing:3,bounding_box_padding:2,always_hide_legend:!1,color:"#B8B8B8",fill_opacity:1,tooltip_positioning:"vertical"},g=t.DataLayers.get("BaseDataLayer");const _={closable:!1,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"{{intervals:state_name|htmlescape}}
    {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}"},h={namespace:{intervals:"intervals"},id:"intervals",type:"intervals",tag:"intervals",id_field:"{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}",start_field:"intervals:start",end_field:"intervals:end",track_split_field:"intervals:state_name",track_label_field:"intervals:state_name",split_tracks:!1,always_hide_legend:!0,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:state_name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],legend:[],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:_},u=t.Layouts.merge({id_field:"{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}",start_field:"intervals:chromStart",end_field:"intervals:chromEnd",track_split_field:"intervals:name",track_label_field:"intervals:name",split_tracks:!0,always_hide_legend:!1,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],tooltip:t.Layouts.merge({html:"Group: {{intervals:name|htmlescape}}
    \nRegion: {{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}}\n{{#if intervals:score}}
    \nScore: {{intervals:score|htmlescape}}{{/if}}"},_)},h),p={id:"intervals",tag:"intervals",min_height:50,height:50,margin:{top:25,right:150,bottom:5,left:70},toolbar:function(){const e=t.Layouts.get("toolbar","standard_panel");return e.widgets.push({type:"toggle_split_tracks",data_layer_id:"intervals",position:"right"}),e}(),axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},legend:{hidden:!0,orientation:"horizontal",origin:{x:50,y:0},pad_from_bottom:5},data_layers:[h]},y=t.Layouts.merge({min_height:120,height:120,data_layers:[u]},p),b={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association"),panels:[t.Layouts.get("panel","association"),t.Layouts.merge({min_height:120,height:120},p),t.Layouts.get("panel","genes")]};t.Adapters.add("IntervalLZ",class extends e{_getURL(t){const e=`?filter=id in ${this._config.source} and chromosome eq '${t.chr}' and start le ${t.end} and end ge ${t.start}`;return`${super._getURL(t)}${e}`}}),t.DataLayers.add("intervals",class extends g{constructor(e){t.Layouts.merge(e,d),super(...arguments),this._previous_categories=[],this._categories=[]}initialize(){super.initialize(),this._statusnodes_group=this.svg.group.append("g").attr("class","lz-data-layer-intervals lz-data-layer-intervals-statusnode"),this._datanodes_group=this.svg.group.append("g").attr("class","lz-data_layer-intervals")}_arrangeTrackSplit(t){const{track_split_field:e}=this.layout,a={};return t.forEach((t=>{const r=t[e];Object.prototype.hasOwnProperty.call(a,r)||(a[r]=[]),a[r].push(t)})),a}_arrangeTracksLinear(t,e=!0){if(e)return[t];const{start_field:a,end_field:r}=this.layout,s=[[]];return t.forEach(((t,e)=>{for(let e=0;e{d[t].forEach((t=>{t[r]=e(t[a]),t[i]=e(t[n]),t[s]=g*this.getTrackHeight()+o,t[l]=t[s]+c,t.track=g}))})),[g,Object.values(d).reduce(((t,e)=>t.concat(e)),[])]}getElementStatusNodeId(t){if(this.layout.split_tracks){const e="object"==typeof t?t.track:t;return`${this.getBaseId()}-statusnode-${e}`.replace(/[^\w]/g,"_")}return null}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}_applyLayoutOptions(){const t=this,e=this._base_layout,a=this.layout,r=e.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function})),s=a.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function}));if(!r)throw new Error("Interval tracks must define a `categorical_bin` color scale");const i=r.parameters.categories.length&&r.parameters.values.length,l=e.legend&&e.legend.length;if(!!i^!!l)throw new Error("To use a manually specified color scheme, both color and legend options must be set.");const n=e.color.find((function(t){return t.scale_function&&"to_rgb"===t.scale_function})),o=n&&n.field,c=this._generateCategoriesFromData(this.data,o);if(!i&&!l){const e=this._makeColorScheme(c);s.parameters.categories=c.map((function(t){return t[0]})),s.parameters.values=e,this.layout.legend=c.map((function(e,a){const r=e[0],i={shape:"rect",width:9,label:e[1],color:s.parameters.values[a]};return i[t.layout.track_split_field]=r,i}))}}render(){this._applyLayoutOptions(),this._previous_categories=this._categories;const[t,e]=this._assignTracks(this.data);this._categories=t;if(!t.every(((t,e)=>t===this._previous_categories[e])))return void this.updateSplitTrackAxis(t);const l=this._applyFilters(e);this._statusnodes_group.selectAll("rect").remove();const n=this._statusnodes_group.selectAll("rect").data(a.range(t.length));if(this.layout.split_tracks){const t=this.getTrackHeight();n.enter().append("rect").attr("class","lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared").attr("rx",this.layout.bounding_box_padding).attr("ry",this.layout.bounding_box_padding).merge(n).attr("id",(t=>this.getElementStatusNodeId(t))).attr("x",0).attr("y",(e=>e*t)).attr("width",this.parent.layout.cliparea.width).attr("height",Math.max(t-this.layout.track_vertical_spacing,1))}n.exit().remove();const o=this._datanodes_group.selectAll("rect").data(l,(t=>t[this.layout.id_field]));o.enter().append("rect").merge(o).attr("id",(t=>this.getElementId(t))).attr("x",(t=>t[r])).attr("y",(t=>t[s])).attr("width",(t=>Math.max(t[i]-t[r],1))).attr("height",this.layout.track_height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),o.exit().remove(),this._datanodes_group.call(this.applyBehaviors.bind(this)),this.parent&&this.parent.legend&&this.parent.legend.render()}_getTooltipPosition(t){return{x_min:t.data[r],x_max:t.data[i],y_min:t.data[s],y_max:t.data[l]}}updateSplitTrackAxis(t){const e=!!this.layout.track_split_legend_to_y_axis&&`y${this.layout.track_split_legend_to_y_axis}`;if(this.layout.split_tracks){const a=+t.length||0,r=+this.layout.track_height||0,s=2*(+this.layout.bounding_box_padding||0)+(+this.layout.track_vertical_spacing||0),i=a*r+(a-1)*s;this.parent.scaleHeightToData(i),e&&this.parent.legend&&(this.parent.legend.hide(),this.parent.layout.axes[e]={render:!0,ticks:[],range:{start:i-this.layout.track_height/2,end:this.layout.track_height/2}},this.layout.legend.forEach((r=>{const s=r[this.layout.track_split_field];let i=t.findIndex((t=>t===s));-1!==i&&("DESC"===this.layout.track_split_order&&(i=Math.abs(i-a-1)),this.parent.layout.axes[e].ticks.push({y:i-1,text:r.label}))})),this.layout.y_axis={axis:this.layout.track_split_legend_to_y_axis,floor:1,ceiling:a}),this.parent_plot.positionPanels()}else e&&this.parent.legend&&(this.layout.always_hide_legend||this.parent.legend.show(),this.parent.layout.axes[e]={render:!1},this.parent.render());return this}toggleSplitTracks(){return this.layout.split_tracks=!this.layout.split_tracks,this.parent.legend&&!this.layout.always_hide_legend&&(this.parent.layout.margin.bottom=5+(this.layout.split_tracks?0:this.parent.legend.layout.height+5)),this.render(),this}_makeColorScheme(t){if(t.find((t=>t[2])))return t.map((t=>c(0,t[2])));const e=t.length;return e<=15?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(233,150,122)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(50,205,50)","rgb(255,69,0)","rgb(255,0,0)"]:e<=18?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]:["rgb(212,212,212)","rgb(128,128,128)","rgb(112,48,160)","rgb(230,184,183)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,102)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,150,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]}_generateCategoriesFromData(t,e){const a=this,r=this._base_layout.legend;if(r&&r.length)return r.map((t=>[t[this.layout.track_split_field],t.label,t.color]));const s={},i=[];return t.forEach((t=>{const r=t[a.layout.track_split_field];Object.prototype.hasOwnProperty.call(s,r)||(s[r]=null,i.push([r,t[this.layout.track_label_field],t[e]]))})),i}}),t.Layouts.add("tooltip","standard_intervals",_),t.Layouts.add("data_layer","intervals",h),t.Layouts.add("data_layer","bed_intervals",u),t.Layouts.add("panel","intervals",p),t.Layouts.add("panel","bed_intervals",y),t.Layouts.add("plot","interval_association",b),t.ScaleFunctions.add("to_rgb",c),t.Widgets.add("toggle_split_tracks",class extends o{constructor(t){if(super(...arguments),t.data_layer_id||(t.data_layer_id="intervals"),!this.parent_panel.data_layers[t.data_layer_id])throw new Error("Toggle split tracks widget specifies an invalid data layer ID")}update(){const t=this.parent_panel.data_layers[this.layout.data_layer_id],e=t.layout.split_tracks?"Merge Tracks":"Split Tracks";return this.button?(this.button.setHtml(e),this.button.show(),this.parent.position(),this):(this.button=new n(this).setColor(this.layout.color).setHtml(e).setTitle("Toggle whether tracks are split apart or merged together").setOnclick((()=>{t.toggleSplitTracks(),this.scale_timeout&&clearTimeout(this.scale_timeout),this.scale_timeout=setTimeout((()=>{this.parent_panel.scaleHeightToData(),this.parent_plot.positionPanels()}),0),this.update()})),this.update())}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const o=n;LzIntervalsTrack=e.default})(); //# sourceMappingURL=lz-intervals-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-parsers.min.js b/dist/ext/lz-parsers.min.js index 8f31a7e3..63de381d 100644 --- a/dist/ext/lz-parsers.min.js +++ b/dist/ext/lz-parsers.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.2 */ +/*! Locuszoom 0.14.0-beta.3 */ var LzParsers;(()=>{"use strict";var e={d:(r,t)=>{for(var l in t)e.o(t,l)&&!e.o(r,l)&&Object.defineProperty(r,l,{enumerable:!0,get:t[l]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r)},r={};e.d(r,{default:()=>d});const t=new Set(["",".","NA","N/A","n/a","nan","-nan","NaN","-NaN","null","NULL","None",null]),l=/([\d.-]+)([\sxeE]*)([0-9-]*)/;function n(e){return Number.isInteger(e)}function o(e,r=t,l=null){return e.map((e=>r.has(e)?l:e))}function a(e){return e.replace(/^chr/g,"").toUpperCase()}function s(e,r=!1){if(null===e)return e;const t=+e;if(r)return t;if(t<0||t>1)throw new Error("p value is not in the allowed range");if(0===t){if("0"===e)return 1/0;let[,r,,t]=e.match(l);return r=+r,t=""!==t?+t:0,0===r?1/0:-(Math.log10(+r)+ +t)}return-Math.log10(t)}function u(e){return null==e||"."===e?null:e}function i(e){return(e=u(e))?+e:null}const c=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function f(e,r=!1){const t=e&&e.match(c);if(t)return t.slice(1);if(r)return null;throw new Error(`Could not understand marker format for ${e}. Should be of format chr:pos or chr:pos_ref/alt`)}function p(e){const r=f(e);if(!r)throw new Error(`Unable to normalize marker format for variant: ${e}`);const[t,l,n,o]=r;let a=`${t}:${l}`;return n&&o&&(a+=`_${n}/${o}`),a}function _(e,r){const t=Array(r.length+1).fill(null).map((()=>Array(e.length+1).fill(null)));for(let r=0;r<=e.length;r+=1)t[0][r]=r;for(let e=0;e<=r.length;e+=1)t[e][0]=e;for(let l=1;l<=r.length;l+=1)for(let n=1;n<=e.length;n+=1){const o=e[n-1]===r[l-1]?0:1;t[l][n]=Math.min(t[l][n-1]+1,t[l-1][n]+1,t[l-1][n-1]+o)}return t[r.length][e.length]}function m(e,r,t=2){let l=t+1,n=null;for(let t=0;t_(o,e))));ae.variant1===r.ld_refvar))}}e.Adapters.add("UserTabixLD",l)}}"undefined"!=typeof LocusZoom&&LocusZoom.use(h);const d={install:h,makeBed12Parser:function({normalize:e=!0}={}){return r=>{const t=r.trim().split("\t");let[l,n,o,s,c,f,p,_,m,h,d,v]=t;if(!(l&&n&&o))throw new Error("Sample data must provide all required BED columns");if(f=u(f),e&&(l=a(l),n=+n+1,o=+o,c=i(c),p=i(p),_=i(_),m=u(m),h=i(h),d=u(d),d=d?d.replace(/,$/,"").split(",").map((e=>+e)):null,v=u(v),v=v?v.replace(/,$/,"").split(",").map((e=>+e+1)):null,d&&v&&h&&(d.length!==h||v.length!==h)))throw new Error("Block size and start information should provide the same number of items as in blockCount");return{chrom:l,chromStart:n,chromEnd:o,name:s,score:c,strand:f,thickStart:p,thickEnd:_,itemRgb:m,blockCount:h,blockSizes:d,blockStarts:v}}},makeGWASParser:function({marker_col:e,chrom_col:r,pos_col:l,ref_col:o,alt_col:u,pvalue_col:i,is_neg_log_pvalue:c=!1,rsid_col:p,beta_col:_,stderr_beta_col:m,allele_freq_col:h,allele_count_col:d,n_samples_col:v,is_alt_effect:g=!0,delimiter:w="\t"}){if(n(e)&&n(r)&&n(l))throw new Error("Must specify either marker OR chr + pos");if(!(n(e)||n(r)&&n(l)))throw new Error("Must specify how to locate marker");if(n(d)&&n(h))throw new Error("Allele count and frequency options are mutually exclusive");if(n(d)&&!n(v))throw new Error("To calculate allele frequency from counts, you must also provide n_samples");return b=>{const k=b.split(w);let y,E,q,A,N,$,S,L=null,z=null,P=null,C=null;if(n(e))[y,E,q,A]=f(k[e-1],!1);else{if(!n(r)||!n(l))throw new Error("Must specify all fields required to identify the variant");y=k[r-1],E=k[l-1]}if(y=a(y),y.startsWith("RS"))throw new Error(`Invalid chromosome specified: value "${y}" is an rsID`);n(o)&&(q=k[o-1]),n(u)&&(A=k[u-1]),n(p)&&(L=k[p-1]),t.has(q)&&(q=null),t.has(A)&&(A=null),t.has(L)?L=null:L&&(L=L.toLowerCase(),L.startsWith("rs")||(L=`rs${L}`));const O=s(k[i-1],c);q=q||null,A=A||null,n(h)&&(N=k[h-1]),n(d)&&($=k[d-1],S=k[v-1]),n(_)&&(z=k[_-1],z=t.has(z)?null:+z),n(m)&&(P=k[m-1],P=t.has(P)?null:+P),(h||d)&&(C=function({freq:e,allele_count:r,n_samples:l,is_alt_effect:n=!0}){if(void 0!==e&&void 0!==r)throw new Error("Frequency and allele count options are mutually exclusive");let o;if(void 0===e&&(t.has(r)||t.has(l)))return null;if(void 0===e&&void 0!==r)o=+r/+l/2;else{if(t.has(e))return null;o=+e}if(o<0||o>1)throw new Error("Allele frequency is not in the allowed range");return n?o:1-o}({freq:N,allele_count:$,n_samples:S,is_alt_effect:g}));const R=q&&A?`_${q}/${A}`:"";return{chromosome:y,position:+E,ref_allele:q?q.toUpperCase():null,alt_allele:A?A.toUpperCase():null,variant:`${y}:${E}${R}`,rsid:L,log_pvalue:O,beta:z,stderr_beta:P,alt_allele_freq:C}}},guessGWAS:function(e,r,t=1){const l=e.map((e=>e?e.toLowerCase():e));l[0].replace("/^#+/","");const n=function(e,r){let t;const l=(e,r,l)=>{const n=o(r.map((r=>r[e])));try{t=n.map((e=>s(e,l)))}catch(e){return!1}return t.every((e=>!Number.isNaN(e)))},n=m(["neg_log_pvalue","log_pvalue","log_pval","logpvalue"],e),a=m(["pvalue","p.value","p-value","pval","p_score","p","p_value"],e);return null!==n&&l(n,r,!0)?{pvalue_col:n+1,is_neg_log_pvalue:!0}:a&&l(a,r,!1)?{pvalue_col:a+1,is_neg_log_pvalue:!1}:null}(l,r);if(!n)return null;l[n.pvalue_col-1]=null;const a=function(e,r){const t=r[0];let l=m(["snpid","marker","markerid","snpmarker","chr:position"],e);if(null!==l&&f(t[l],!0))return l+=1,{marker_col:l};const n=e.slice(),o=[["chrom_col",["chrom","chr","chromosome"],!0],["pos_col",["position","pos","begin","beg","bp","end","ps","base_pair_location"],!0],["ref_col",["A1","ref","reference","allele0","allele1"],!1],["alt_col",["A2","alt","alternate","allele1","allele2"],!1]],a={};for(let e=0;e{l[a[e]]=null}));const u=function(e,r){function t(e,r){const t=o(r.map((r=>r[e])));let l;try{l=t.filter((e=>null!==e)).map((e=>+e))}catch(e){return!1}return l.every((e=>!Number.isNaN(e)))}const l=m(["beta","effect_size","alt_effsize","effect"],e,0),n=m(["stderr_beta","stderr","sebeta","effect_size_sd","se","standard_error"],e,0),a={};return null!==l&&t(l,r)&&(a.beta_col=l+1),null!==n&&t(n,r)&&(a.stderr_beta_col=n+1),a}(l,r);return n&&a?Object.assign({},n,a,u||{}):null},makePlinkLdParser:function({normalize:e=!0}={}){return r=>{let[t,l,n,o,s,u,i]=r.trim().split("\t");return e&&(t=a(t),o=a(o),n=p(n),u=p(u),l=+l,s=+s,i=+i),{chromosome1:t,position1:l,variant1:n,chromosome2:o,position2:s,variant2:u,correlation:i}}}};LzParsers=r.default})(); //# sourceMappingURL=lz-parsers.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-parsers.min.js.map b/dist/ext/lz-parsers.min.js.map index b443db68..36db2cba 100644 --- a/dist/ext/lz-parsers.min.js.map +++ b/dist/ext/lz-parsers.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-parsers/utils.js","webpack://[name]/./esm/ext/lz-parsers/bed.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/ext/lz-parsers/gwas/sniffers.js","webpack://[name]/./esm/ext/lz-parsers/index.js","webpack://[name]/./esm/ext/lz-parsers/gwas/parsers.js","webpack://[name]/./esm/ext/lz-parsers/ld.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Set","REGEX_PVAL","has","num","Number","isInteger","missingToNull","values","nulls","placeholder","map","v","normalizeChr","chromosome","replace","toUpperCase","parsePvalToLog","value","is_neg_log","val","Error","Infinity","base","exponent","match","Math","log10","_bedMissing","_hasNum","REGEX_MARKER","parseMarker","test","slice","normalizeMarker","variant","chrom","pos","ref","alt","normalized","levenshtein","a","b","distanceMatrix","Array","length","fill","i","j","indicator","min","findColumn","column_synonyms","header_names","threshold","best_score","best_match","header","score","s","install","LocusZoom","Adapters","TabixUrlSource","LDServer","UserTabixLD","config","limit_fields","super","state","assoc_data","_buildRequestOptions","arguments","ld_refvar","__find_ld_refvar","_skip_request","options","Promise","resolve","_performRequest","records","filter","item","add","use","makeBed12Parser","normalize","line","tokens","trim","split","chromStart","chromEnd","name","strand","thickStart","thickEnd","itemRgb","blockCount","blockSizes","blockStarts","marker_col","chrom_col","pos_col","ref_col","alt_col","pvalue_col","is_neg_log_pvalue","rsid_col","beta_col","stderr_beta_col","allele_freq_col","allele_count_col","n_samples_col","is_alt_effect","delimiter","fields","chr","freq","allele_count","n_samples","rsid","beta","stderr_beta","alt_allele_freq","startsWith","toLowerCase","log_pval","undefined","result","parseAlleleFrequency","ref_alt","position","ref_allele","alt_allele","log_pvalue","header_row","data_rows","offset","headers","pval_config","ps","validateP","col","data","is_log","cleaned_vals","row","p","e","every","isNaN","log_p_col","p_col","getPvalColumn","position_config","first_row","headers_marked","find","col_name","choices","is_required","getChromPosRefAltColumns","keys","forEach","beta_config","validate_numeric","nums","ret","getEffectSizeColumns","assign","chromosome1","position1","variant1","chromosome2","position2","variant2","correlation"],"mappings":"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","file":"ext/lz-parsers.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Constant values used by GWAS parser\n */\n\n/**\n * @private\n */\nconst MISSING_VALUES = new Set(['', '.', 'NA', 'N/A', 'n/a', 'nan', '-nan', 'NaN', '-NaN', 'null', 'NULL', 'None', null]);\n\n/**\n * @private\n */\nconst REGEX_PVAL = /([\\d.-]+)([\\sxeE]*)([0-9-]*)/;\n\n\n/**\n * Utility helper that checks for the presence of a numeric value (incl 0),\n * eg \"has column specified\"\n * @private\n * @param num\n * @returns {boolean}\n */\nfunction has(num) {\n return Number.isInteger(num);\n}\n\n/**\n * Convert all missing values to a standardized input form\n * Useful for columns like pvalue, where a missing value explicitly allowed\n * @private\n */\nfunction missingToNull(values, nulls = MISSING_VALUES, placeholder = null) {\n // TODO Make this operate on a single value; cache for efficiency?\n return values.map((v) => (nulls.has(v) ? placeholder : v));\n}\n\n/**\n * Normalize chromosome representations\n * @private\n * @param {String} chromosome\n */\nfunction normalizeChr(chromosome) {\n return chromosome.replace(/^chr/g, '').toUpperCase();\n}\n\n/**\n * Parse (and validate) a given number, and return the -log10 pvalue.\n * @private\n * @param value\n * @param {boolean} is_neg_log\n * @returns {number|null} The -log10 pvalue\n */\nfunction parsePvalToLog(value, is_neg_log = false) {\n // TODO: In future, generalize this for other values prone to underflow\n if (value === null) {\n return value;\n }\n const val = +value;\n if (is_neg_log) { // Take as is\n return val;\n }\n // Regular pvalue: validate and convert\n if (val < 0 || val > 1) {\n throw new Error('p value is not in the allowed range');\n }\n // 0-values are explicitly allowed and will convert to infinity by design, as they often\n // indicate underflow errors in the input data.\n if (val === 0) {\n // Determine whether underflow is due to the source data, or value conversion\n if (value === '0') {\n // The source data is bad, so insert an obvious placeholder value\n return Infinity;\n }\n // h/t @welchr: aggressively turn the underflowing string value into -log10 via regex\n // Only do this if absolutely necessary, because it is a performance hit\n\n let [, base, , exponent] = value.match(REGEX_PVAL);\n base = +base;\n\n if (exponent !== '') {\n exponent = +exponent;\n } else {\n exponent = 0;\n }\n if (base === 0) {\n return Infinity;\n }\n return -(Math.log10(+base) + +exponent);\n }\n return -Math.log10(val);\n}\n\n/**\n * @private\n */\nfunction parseAlleleFrequency({ freq, allele_count, n_samples, is_alt_effect = true }) {\n if (freq !== undefined && allele_count !== undefined) {\n throw new Error('Frequency and allele count options are mutually exclusive');\n }\n\n let result;\n if (freq === undefined && (MISSING_VALUES.has(allele_count) || MISSING_VALUES.has(n_samples))) {\n // Allele count parsing\n return null;\n }\n if (freq === undefined && allele_count !== undefined) {\n result = +allele_count / +n_samples / 2;\n } else if (MISSING_VALUES.has(freq)) { // Frequency-based parsing\n return null;\n } else {\n result = +freq;\n }\n\n // No matter how the frequency is specified, this stuff is always done\n if (result < 0 || result > 1) {\n throw new Error('Allele frequency is not in the allowed range');\n }\n if (!is_alt_effect) { // Orient the frequency to the alt allele\n return 1 - result;\n }\n return result;\n}\n\nexport {\n MISSING_VALUES,\n missingToNull as _missingToNull,\n has,\n normalizeChr,\n // Exports for unit testing\n parseAlleleFrequency,\n parsePvalToLog,\n};\n","/**\n * Parse BED-family files, which have 3-12 columns\n * https://genome.ucsc.edu/FAQ/FAQformat.html#format1\n */\n\nimport {normalizeChr} from './utils';\n\n/**\n * @private\n */\nfunction _bedMissing(value) {\n // BED files specify . as the missing/ null value character\n if (value === null || value === undefined || value === '.') {\n return null;\n }\n return value;\n}\n\n/**\n * @private\n */\nfunction _hasNum(value) {\n // Return a number, or null if value marked as missing\n value = _bedMissing(value);\n return value ? +value : null;\n}\n\n/**\n * Parse a BED file, according to the widely used UCSC (quasi-)specification\n *\n * NOTE: This original version is aimed at tabix region queries, and carries an implicit assumption that data is the\n * only thing that will be parsed. It makes no attempt to identify or handle header rows / metadata fields.\n *\n * @function\n * @alias module:ext/lz-parsers~makeBed12Parser\n * @param {object} options\n * @param {Boolean} options.normalize Whether to normalize the output to the format expected by LocusZoom (eg type coercion\n * for numbers, removing chr chromosome prefixes, and using 1-based and inclusive coordinates instead of 0-based disjoint intervals)\n * @return function A configured parser function that runs on one line of text from an input file\n */\nfunction makeBed12Parser({normalize = true} = {}) {\n /*\n * @param {String} line The line of text to be parsed\n */\n return (line) => {\n const tokens = line.trim().split('\\t');\n // The BED file format has 12 standardized columns. 3 are required and 9 are optional. At present, we will not\n // attempt to parse any remaining tokens, or nonstandard files that reuse columns with a different meaning.\n // https://en.wikipedia.org/wiki/BED_(file_format)\n let [\n chrom,\n chromStart,\n chromEnd,\n name,\n score,\n strand,\n thickStart,\n thickEnd,\n itemRgb,\n blockCount,\n blockSizes,\n blockStarts,\n ] = tokens;\n\n if (!(chrom && chromStart && chromEnd)) {\n throw new Error('Sample data must provide all required BED columns');\n }\n\n strand = _bedMissing(strand);\n\n if (normalize) {\n // Mandatory fields\n chrom = normalizeChr(chrom);\n chromStart = +chromStart + 1; // BED is 0 based start, but LZ plots start at 1\n chromEnd = +chromEnd; // 0-based positions, intervals exclude end position\n\n // Optional fields, require checking for blanks\n score = _hasNum(score);\n thickStart = _hasNum(thickStart);\n thickEnd = _hasNum(thickEnd);\n\n itemRgb = _bedMissing(itemRgb);\n\n // LocusZoom doesn't use these fields for rendering. Parsing below is theoretical/best-effort.\n blockCount = _hasNum(blockCount);\n\n blockSizes = _bedMissing(blockSizes);\n blockSizes = !blockSizes ? null : blockSizes.replace(/,$/, '').split(',').map((value) => +value); // Comma separated list of sizes -> array of integers\n\n blockStarts = _bedMissing(blockStarts);\n blockStarts = !blockStarts ? null : blockStarts.replace(/,$/, '').split(',').map((value) => +value + 1); // Comma separated list of sizes -> array of integers (start positions)\n\n if (blockSizes && blockStarts && blockCount && (blockSizes.length !== blockCount || blockStarts.length !== blockCount)) {\n throw new Error('Block size and start information should provide the same number of items as in blockCount');\n }\n }\n return {\n chrom,\n chromStart,\n chromEnd,\n name,\n score,\n strand,\n thickStart,\n thickEnd,\n itemRgb,\n blockCount,\n blockSizes,\n blockStarts,\n };\n };\n}\n\nexport { makeBed12Parser };\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Sniffers: auto detect file format and parsing options for GWAS files.\n */\n\nimport { parseMarker } from '../../../helpers/parse';\nimport { MISSING_VALUES, parsePvalToLog, _missingToNull } from '../utils';\n\n/**\n * @private\n */\nfunction isNumeric(val) {\n // Check whether an unparsed string is a numeric value\"\n if (MISSING_VALUES.has(val)) {\n return true;\n }\n return !Number.isNaN(+val);\n}\n\nfunction isHeader(row, { comment_char = '#', delimiter = '\\t' } = {}) {\n // This assumes two basic rules: the line is not a comment, and gwas data is more likely\n // to be numeric than headers\n return row.startsWith(comment_char) || row.split(delimiter).every((item) => !isNumeric(item));\n}\n\n/**\n * Compute the levenshtein distance between two strings. Useful for finding the single best column\n * name that matches a given rule.\n * @private\n */\nfunction levenshtein(a, b) { // https://github.com/trekhleb/javascript-algorithms\n // Create empty edit distance matrix for all possible modifications of\n // substrings of a to substrings of b.\n const distanceMatrix = Array(b.length + 1)\n .fill(null)\n .map(() => Array(a.length + 1)\n .fill(null));\n\n // Fill the first row of the matrix.\n // If this is first row then we're transforming empty string to a.\n // In this case the number of transformations equals to size of a substring.\n for (let i = 0; i <= a.length; i += 1) {\n distanceMatrix[0][i] = i;\n }\n\n // Fill the first column of the matrix.\n // If this is first column then we're transforming empty string to b.\n // In this case the number of transformations equals to size of b substring.\n for (let j = 0; j <= b.length; j += 1) {\n distanceMatrix[j][0] = j;\n }\n\n for (let j = 1; j <= b.length; j += 1) {\n for (let i = 1; i <= a.length; i += 1) {\n const indicator = a[i - 1] === b[j - 1] ? 0 : 1;\n distanceMatrix[j][i] = Math.min(\n distanceMatrix[j][i - 1] + 1, // deletion\n distanceMatrix[j - 1][i] + 1, // insertion\n distanceMatrix[j - 1][i - 1] + indicator // substitution\n );\n }\n }\n return distanceMatrix[b.length][a.length];\n}\n\n/**\n * Return the index of the first column name that meets acceptance criteria\n * @private\n * @param {String[]} column_synonyms\n * @param {String[]}header_names\n * @param {Number} threshold Tolerance for fuzzy matching (# edits)\n * @return {Number|null} Index of the best matching column, or null if no match possible\n */\nfunction findColumn(column_synonyms, header_names, threshold = 2) {\n // Find the column name that best matches\n let best_score = threshold + 1;\n let best_match = null;\n for (let i = 0; i < header_names.length; i++) {\n const header = header_names[i];\n if (header === null) {\n // If header is empty, don't consider it for a match\n // Nulling a header provides a way to exclude something from future searching\n continue; // eslint-disable-line no-continue\n }\n const score = Math.min(...column_synonyms.map((s) => levenshtein(header, s)));\n if (score < best_score) {\n best_score = score;\n best_match = i;\n }\n }\n return best_match;\n}\n\n\n/**\n * Return parser configuration for pvalues\n *\n * Returns 1-based column indices, for compatibility with parsers\n * @private\n * @param header_row\n * @param data_rows\n * @returns {{}}\n */\nfunction getPvalColumn(header_row, data_rows) {\n // TODO: Allow overrides\n const LOGPVALUE_FIELDS = ['neg_log_pvalue', 'log_pvalue', 'log_pval', 'logpvalue'];\n const PVALUE_FIELDS = ['pvalue', 'p.value', 'p-value', 'pval', 'p_score', 'p', 'p_value'];\n\n let ps;\n const validateP = (col, data, is_log) => { // Validate pvalues\n const cleaned_vals = _missingToNull(data.map((row) => row[col]));\n try {\n ps = cleaned_vals.map((p) => parsePvalToLog(p, is_log));\n } catch (e) {\n return false;\n }\n return ps.every((val) => !Number.isNaN(val));\n };\n\n const log_p_col = findColumn(LOGPVALUE_FIELDS, header_row);\n const p_col = findColumn(PVALUE_FIELDS, header_row);\n\n if (log_p_col !== null && validateP(log_p_col, data_rows, true)) {\n return {\n pvalue_col: log_p_col + 1,\n is_neg_log_pvalue: true,\n };\n }\n if (p_col && validateP(p_col, data_rows, false)) {\n return {\n pvalue_col: p_col + 1,\n is_neg_log_pvalue: false,\n };\n }\n // Could not auto-determine an appropriate pvalue column\n return null;\n}\n\n/**\n * @private\n */\nfunction getChromPosRefAltColumns(header_row, data_rows) {\n // Returns 1-based column indices, for compatibility with parsers\n // Get from either a marker, or 4 separate columns\n const MARKER_FIELDS = ['snpid', 'marker', 'markerid', 'snpmarker', 'chr:position'];\n const CHR_FIELDS = ['chrom', 'chr', 'chromosome'];\n const POS_FIELDS = ['position', 'pos', 'begin', 'beg', 'bp', 'end', 'ps', 'base_pair_location'];\n\n // TODO: How to handle orienting ref vs effect?\n // Order matters: consider ambiguous field names for ref before alt\n const REF_FIELDS = ['A1', 'ref', 'reference', 'allele0', 'allele1'];\n const ALT_FIELDS = ['A2', 'alt', 'alternate', 'allele1', 'allele2'];\n\n const first_row = data_rows[0];\n let marker_col = findColumn(MARKER_FIELDS, header_row);\n if (marker_col !== null && parseMarker(first_row[marker_col], true)) {\n marker_col += 1;\n return { marker_col };\n }\n\n // If single columns were incomplete, attempt to auto detect 4 separate columns. All 4 must\n // be found for this function to report a match.\n const headers_marked = header_row.slice();\n const find = [\n ['chrom_col', CHR_FIELDS, true],\n ['pos_col', POS_FIELDS, true],\n ['ref_col', REF_FIELDS, false],\n ['alt_col', ALT_FIELDS, false],\n ];\n const config = {};\n for (let i = 0; i < find.length; i++) {\n const [col_name, choices, is_required] = find[i];\n const col = findColumn(choices, headers_marked);\n if (col === null && is_required) {\n return null;\n }\n if (col !== null) {\n config[col_name] = col + 1;\n // Once a column has been assigned, remove it from consideration\n headers_marked[col] = null;\n }\n }\n return config;\n}\n\n/**\n * Identify which columns contain effect size (beta) and stderr of the effect size\n * @private\n * @param {String[]} header_names\n * @param {Array[]} data_rows\n * @returns {{}}\n */\nfunction getEffectSizeColumns(header_names, data_rows) {\n const BETA_FIELDS = ['beta', 'effect_size', 'alt_effsize', 'effect'];\n const STDERR_BETA_FIELDS = ['stderr_beta', 'stderr', 'sebeta', 'effect_size_sd', 'se', 'standard_error'];\n\n function validate_numeric(col, data) {\n const cleaned_vals = _missingToNull(data.map((row) => row[col]));\n let nums;\n try {\n nums = cleaned_vals.filter((val) => val !== null).map((val) => +val);\n } catch (e) {\n return false;\n }\n return nums.every((val) => !Number.isNaN(val));\n }\n\n const beta_col = findColumn(BETA_FIELDS, header_names, 0);\n const stderr_beta_col = findColumn(STDERR_BETA_FIELDS, header_names, 0);\n\n const ret = {};\n if (beta_col !== null && validate_numeric(beta_col, data_rows)) {\n ret.beta_col = beta_col + 1;\n }\n if (stderr_beta_col !== null && validate_numeric(stderr_beta_col, data_rows)) {\n ret.stderr_beta_col = stderr_beta_col + 1;\n }\n return ret;\n}\n\n/**\n * Attempt to guess the correct parser settings given a set of header rows and a set of data rows\n * @private\n * @param {String[]} header_row\n * @param {String[][]} data_rows\n * @param {int} offset Used to convert between 0 and 1-based indexing.\n * @return {Object|null} Returns null if a complete configuration could not suggested.\n */\nfunction guessGWAS(header_row, data_rows, offset = 1) {\n // 1. Find a specific set of info: marker OR chr/pos/ref/alt ; pvalue OR log_pvalue\n // 2. Validate that we will be able to parse the required info: fields present and make sense\n // 3. Based on the field names selected, attempt to infer meaning: verify whether log is used,\n // and check ref/alt vs effect/noneffect\n // 4. Return a parser config object if all tests pass, OR null.\n\n // Normalize case and remove leading comment marks from line for easier comparison\n const headers = header_row.map((item) => (item ? item.toLowerCase() : item));\n headers[0].replace('/^#+/', '');\n // Lists of fields are drawn from Encore (AssocResultReader) and Pheweb (conf_utils.py)\n const pval_config = getPvalColumn(headers, data_rows, offset);\n if (!pval_config) {\n return null;\n }\n headers[pval_config.pvalue_col - 1] = null; // Remove this column from consideration\n const position_config = getChromPosRefAltColumns(headers, data_rows);\n if (!position_config) {\n return null;\n }\n // Remove the position config from consideration for future matches\n Object.keys(position_config).forEach((key) => {\n headers[position_config[key]] = null;\n });\n\n const beta_config = getEffectSizeColumns(headers, data_rows);\n\n if (pval_config && position_config) {\n return Object.assign({}, pval_config, position_config, beta_config || {});\n }\n return null;\n}\n\nexport {\n // Public members\n guessGWAS,\n // Symbols exported for testing\n isHeader as _isHeader,\n getPvalColumn as _getPvalColumn,\n findColumn as _findColumn,\n levenshtein as _levenshtein,\n};\n","/**\n * Optional LocusZoom extension: must be included separately, and after LocusZoom has been loaded\n *\n * This plugin exports helper function, as well as a few optional extra helpers for rendering the plot. The GWAS parsers can be used without registering the plugin.\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, import the helper functions and use them with your layout:\n *\n * ```\n * import { install, makeGWASParser, makeBed12Parser, makePlinkLDParser } from 'locuszoom/esm/ext/lz-parsers';\n * LocusZoom.use(install);\n * ```\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~UserTabixLD} (if the {@link module:ext/lz-tabix-source} extension is loaded first)\n *\n * @module ext/lz-parsers\n */\n\nimport { makeBed12Parser } from './bed';\nimport { makeGWASParser } from './gwas/parsers';\nimport { guessGWAS } from './gwas/sniffers';\nimport { makePlinkLdParser } from './ld';\n\n\n// Most of this plugin consists of standalone functions. But we can add a few simple custom classes to the registry that help to use parsed output\nfunction install(LocusZoom) {\n if (LocusZoom.Adapters.has('TabixUrlSource')) {\n // Custom Tabix adapter depends on another extension being loaded first\n const TabixUrlSource = LocusZoom.Adapters.get('TabixUrlSource');\n const LDServer = LocusZoom.Adapters.get('LDServer');\n\n /**\n * Load user-provided LD from a tabix file, and filter the returned set of records based on a reference variant. (will attempt to choose a reference variant based on the most significant association variant, if no state.ldrefvar is specified)\n * @public\n * @alias module:LocusZoom_Adapters~UserTabixLD\n * @extends module:LocusZoom_Adapters~TabixUrlSource\n * @see {@link module:ext/lz-tabix-source} for required extension and installation instructions\n * @see {@link module:ext/lz-parsers} for required extension and installation instructions\n */\n class UserTabixLD extends TabixUrlSource {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n base._skip_request = true;\n return base;\n }\n\n // NOTE: Reuses a method from another adapter to mix in functionality\n base.ld_refvar = LDServer.prototype.__find_ld_refvar(state, assoc_data);\n return base;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n return Promise.resolve([]);\n }\n return super._performRequest(options);\n }\n\n _annotateRecords(records, options) {\n // A single PLINK LD file could contain several reference variants (SNP_A) in the same region.\n // Only show LD relative to the user-selected refvar in this plot.\n return records.filter((item) => item['variant1'] === options.ld_refvar);\n }\n }\n\n\n LocusZoom.Adapters.add('UserTabixLD', UserTabixLD);\n }\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n// Support UMD (single symbol export)\nconst all = { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser };\n\nexport default all;\n\nexport { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser };\n","import {parseMarker} from '../../../helpers/parse';\n\nimport {\n MISSING_VALUES,\n has,\n parseAlleleFrequency,\n parsePvalToLog, normalizeChr,\n} from '../utils';\n\n\n/**\n * Specify how to parse a GWAS file, given certain column information.\n * Outputs an object with fields in portal API format.\n *\n * All column options must be provided as 1-indexed column IDs (human-friendly argument values)\n * @function\n * @alias module:ext/lz-parsers~makeGWASParser\n * @param options\n * @param [options.marker_col] A single identifier that specifies all of chrom, pos, ref, and alt as a single string field. Eg 1:23_A/C\n * @param [options.chrom_col] Chromosome\n * @param [options.pos_col] Position\n * @param [options.ref_col] Reference allele (relative to human reference genome, eg GRCh37 or 38).\n * @param [options.alt_col] Alt allele. Some programs specify generic A1/A2 instead; it is the job of the user to identify which columns of this GWAS are ref and alt.\n * @param [options.rsid_col] rsID\n * @param options.pvalue_col p-value (or -log10p)\n * @param [options.beta_col]\n * @param [options.stderr_beta_col]\n * @param [options.allele_freq_col] Specify allele frequencies directly\n * @param [options.allele_count_col] Specify allele frequencies in terms of count and n_samples\n * @param [options.n_samples_col]\n * @param [options.is_alt_effect=true] Some programs specify beta and frequency information in terms of ref, others alt. Identify effect allele to orient values to the correct allele.\n * @param [options.is_neg_log_pvalue=false]\n * @param [options.delimiter='\\t'] Since this parser is usually used with tabix data, this is rarely changed (tabix does not accept other delimiters)\n * @return {function(string)} A parser function that can be called on each line of text with the provided options\n */\nfunction makeGWASParser(\n {\n // Required fields\n marker_col, // Identify the variant: marker OR chrom/pos/ref/alt\n chrom_col,\n pos_col,\n ref_col,\n alt_col,\n pvalue_col, // pvalue (or log_pvalue; see options below)\n // Optional fields\n is_neg_log_pvalue = false,\n rsid_col,\n beta_col,\n stderr_beta_col,\n allele_freq_col, // Frequency: given directly, OR in terms of counts\n allele_count_col,\n n_samples_col,\n is_alt_effect = true, // whether effect allele is oriented towards alt. We don't support files like METAL, where ref/alt may switch places per line of the file\n delimiter = '\\t',\n }\n) {\n // Column IDs should be 1-indexed (human friendly)\n if (has(marker_col) && has(chrom_col) && has(pos_col)) {\n throw new Error('Must specify either marker OR chr + pos');\n }\n if (!(has(marker_col) || (has(chrom_col) && has(pos_col)))) {\n throw new Error('Must specify how to locate marker');\n }\n\n if (has(allele_count_col) && has(allele_freq_col)) {\n throw new Error('Allele count and frequency options are mutually exclusive');\n }\n if (has(allele_count_col) && !has(n_samples_col)) {\n throw new Error('To calculate allele frequency from counts, you must also provide n_samples');\n }\n\n\n return (line) => {\n const fields = line.split(delimiter);\n let chr;\n let pos;\n let ref;\n let alt;\n let rsid = null;\n\n let freq;\n let beta = null;\n let stderr_beta = null;\n let alt_allele_freq = null;\n let allele_count;\n let n_samples;\n\n if (has(marker_col)) {\n [chr, pos, ref, alt] = parseMarker(fields[marker_col - 1], false);\n } else if (has(chrom_col) && has(pos_col)) {\n chr = fields[chrom_col - 1];\n pos = fields[pos_col - 1];\n } else {\n throw new Error('Must specify all fields required to identify the variant');\n }\n\n chr = normalizeChr(chr);\n if (chr.startsWith('RS')) {\n throw new Error(`Invalid chromosome specified: value \"${chr}\" is an rsID`);\n }\n\n if (has(ref_col)) {\n ref = fields[ref_col - 1];\n }\n\n if (has(alt_col)) {\n alt = fields[alt_col - 1];\n }\n\n if (has(rsid_col)) {\n rsid = fields[rsid_col - 1];\n }\n\n if (MISSING_VALUES.has(ref)) {\n ref = null;\n }\n if (MISSING_VALUES.has(alt)) {\n alt = null;\n }\n\n if (MISSING_VALUES.has(rsid)) {\n rsid = null;\n } else if (rsid) {\n rsid = rsid.toLowerCase();\n if (!rsid.startsWith('rs')) {\n rsid = `rs${rsid}`;\n }\n }\n\n const log_pval = parsePvalToLog(fields[pvalue_col - 1], is_neg_log_pvalue);\n ref = ref || null;\n alt = alt || null;\n\n if (has(allele_freq_col)) {\n freq = fields[allele_freq_col - 1];\n }\n if (has(allele_count_col)) {\n allele_count = fields[allele_count_col - 1];\n n_samples = fields[n_samples_col - 1];\n }\n\n if (has(beta_col)) {\n beta = fields[beta_col - 1];\n beta = MISSING_VALUES.has(beta) ? null : (+beta);\n }\n\n if (has(stderr_beta_col)) {\n stderr_beta = fields[stderr_beta_col - 1];\n stderr_beta = MISSING_VALUES.has(stderr_beta) ? null : (+stderr_beta);\n }\n\n if (allele_freq_col || allele_count_col) {\n alt_allele_freq = parseAlleleFrequency({\n freq,\n allele_count,\n n_samples,\n is_alt_effect,\n });\n }\n const ref_alt = (ref && alt) ? `_${ref}/${alt}` : '';\n return {\n chromosome: chr,\n position: +pos,\n ref_allele: ref ? ref.toUpperCase() : null,\n alt_allele: alt ? alt.toUpperCase() : null,\n variant: `${chr}:${pos}${ref_alt}`,\n rsid,\n log_pvalue: log_pval,\n beta,\n stderr_beta,\n alt_allele_freq,\n };\n };\n}\n\n\nexport { makeGWASParser };\n","/**\n * Parsers for custom user-specified LD\n */\n\nimport {normalizeChr} from './utils';\nimport {normalizeMarker} from '../../helpers/parse';\n\n/**\n * Parse the output of plink v1.9 R2 calculations relative to one (or a few) target SNPs.\n * See: https://www.cog-genomics.org/plink/1.9/ld and\n * reformatting commands at https://www.cog-genomics.org/plink/1.9/other\n * @function\n * @alias module:ext/lz-parsers~makePlinkLdParser\n * @param {object} options\n * @param {boolean} [options.normalize=true] Normalize fields to expected datatypes and format; if false, returns raw strings as given in the file\n * @return {function} A configured parser function that runs on one line of text from an input file\n */\nfunction makePlinkLdParser({normalize = true} = {}) {\n return (line) => {\n // Sample headers are below: SNP_A and SNP_B are based on ID column of the VCF\n // CHR_A BP_A SNP_A CHR_B BP_B SNP_B R2\n let [chromosome1, position1, variant1, chromosome2, position2, variant2, correlation] = line.trim().split('\\t');\n if (normalize) {\n chromosome1 = normalizeChr(chromosome1);\n chromosome2 = normalizeChr(chromosome2);\n variant1 = normalizeMarker(variant1);\n variant2 = normalizeMarker(variant2);\n position1 = +position1;\n position2 = +position2;\n correlation = +correlation;\n }\n\n return {chromosome1, position1, variant1, chromosome2, position2, variant2, correlation};\n };\n}\n\nexport { makePlinkLdParser };\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/./esm/ext/lz-parsers/utils.js","webpack://[name]/./esm/ext/lz-parsers/bed.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/ext/lz-parsers/gwas/sniffers.js","webpack://[name]/./esm/ext/lz-parsers/index.js","webpack://[name]/./esm/ext/lz-parsers/gwas/parsers.js","webpack://[name]/./esm/ext/lz-parsers/ld.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Set","REGEX_PVAL","has","num","Number","isInteger","missingToNull","values","nulls","placeholder","map","v","normalizeChr","chromosome","replace","toUpperCase","parsePvalToLog","value","is_neg_log","val","Error","Infinity","base","exponent","match","Math","log10","_bedMissing","_hasNum","REGEX_MARKER","parseMarker","test","slice","normalizeMarker","variant","chrom","pos","ref","alt","normalized","levenshtein","a","b","distanceMatrix","Array","length","fill","i","j","indicator","min","findColumn","column_synonyms","header_names","threshold","best_score","best_match","header","score","s","install","LocusZoom","Adapters","TabixUrlSource","LDServer","UserTabixLD","config","limit_fields","super","state","assoc_data","_buildRequestOptions","arguments","ld_refvar","__find_ld_refvar","_skip_request","options","Promise","resolve","_performRequest","records","filter","item","add","use","makeBed12Parser","normalize","line","tokens","trim","split","chromStart","chromEnd","name","strand","thickStart","thickEnd","itemRgb","blockCount","blockSizes","blockStarts","marker_col","chrom_col","pos_col","ref_col","alt_col","pvalue_col","is_neg_log_pvalue","rsid_col","beta_col","stderr_beta_col","allele_freq_col","allele_count_col","n_samples_col","is_alt_effect","delimiter","fields","chr","freq","allele_count","n_samples","rsid","beta","stderr_beta","alt_allele_freq","startsWith","toLowerCase","log_pval","undefined","result","parseAlleleFrequency","ref_alt","position","ref_allele","alt_allele","log_pvalue","header_row","data_rows","offset","headers","pval_config","ps","validateP","col","data","is_log","cleaned_vals","row","p","e","every","isNaN","log_p_col","p_col","getPvalColumn","position_config","first_row","headers_marked","find","col_name","choices","is_required","getChromPosRefAltColumns","keys","forEach","beta_config","validate_numeric","nums","ret","getEffectSizeColumns","assign","chromosome1","position1","variant1","chromosome2","position2","variant2","correlation"],"mappings":"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","file":"ext/lz-parsers.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/**\n * Constant values used by GWAS parser\n */\n\n/**\n * @private\n */\nconst MISSING_VALUES = new Set(['', '.', 'NA', 'N/A', 'n/a', 'nan', '-nan', 'NaN', '-NaN', 'null', 'NULL', 'None', null]);\n\n/**\n * @private\n */\nconst REGEX_PVAL = /([\\d.-]+)([\\sxeE]*)([0-9-]*)/;\n\n\n/**\n * Utility helper that checks for the presence of a numeric value (incl 0),\n * eg \"has column specified\"\n * @private\n * @param num\n * @returns {boolean}\n */\nfunction has(num) {\n return Number.isInteger(num);\n}\n\n/**\n * Convert all missing values to a standardized input form\n * Useful for columns like pvalue, where a missing value explicitly allowed\n * @private\n */\nfunction missingToNull(values, nulls = MISSING_VALUES, placeholder = null) {\n // TODO Make this operate on a single value; cache for efficiency?\n return values.map((v) => (nulls.has(v) ? placeholder : v));\n}\n\n/**\n * Normalize chromosome representations\n * @private\n * @param {String} chromosome\n */\nfunction normalizeChr(chromosome) {\n return chromosome.replace(/^chr/g, '').toUpperCase();\n}\n\n/**\n * Parse (and validate) a given number, and return the -log10 pvalue.\n * @private\n * @param value\n * @param {boolean} is_neg_log\n * @returns {number|null} The -log10 pvalue\n */\nfunction parsePvalToLog(value, is_neg_log = false) {\n // TODO: In future, generalize this for other values prone to underflow\n if (value === null) {\n return value;\n }\n const val = +value;\n if (is_neg_log) { // Take as is\n return val;\n }\n // Regular pvalue: validate and convert\n if (val < 0 || val > 1) {\n throw new Error('p value is not in the allowed range');\n }\n // 0-values are explicitly allowed and will convert to infinity by design, as they often\n // indicate underflow errors in the input data.\n if (val === 0) {\n // Determine whether underflow is due to the source data, or value conversion\n if (value === '0') {\n // The source data is bad, so insert an obvious placeholder value\n return Infinity;\n }\n // h/t @welchr: aggressively turn the underflowing string value into -log10 via regex\n // Only do this if absolutely necessary, because it is a performance hit\n\n let [, base, , exponent] = value.match(REGEX_PVAL);\n base = +base;\n\n if (exponent !== '') {\n exponent = +exponent;\n } else {\n exponent = 0;\n }\n if (base === 0) {\n return Infinity;\n }\n return -(Math.log10(+base) + +exponent);\n }\n return -Math.log10(val);\n}\n\n/**\n * @private\n */\nfunction parseAlleleFrequency({ freq, allele_count, n_samples, is_alt_effect = true }) {\n if (freq !== undefined && allele_count !== undefined) {\n throw new Error('Frequency and allele count options are mutually exclusive');\n }\n\n let result;\n if (freq === undefined && (MISSING_VALUES.has(allele_count) || MISSING_VALUES.has(n_samples))) {\n // Allele count parsing\n return null;\n }\n if (freq === undefined && allele_count !== undefined) {\n result = +allele_count / +n_samples / 2;\n } else if (MISSING_VALUES.has(freq)) { // Frequency-based parsing\n return null;\n } else {\n result = +freq;\n }\n\n // No matter how the frequency is specified, this stuff is always done\n if (result < 0 || result > 1) {\n throw new Error('Allele frequency is not in the allowed range');\n }\n if (!is_alt_effect) { // Orient the frequency to the alt allele\n return 1 - result;\n }\n return result;\n}\n\nexport {\n MISSING_VALUES,\n missingToNull as _missingToNull,\n has,\n normalizeChr,\n // Exports for unit testing\n parseAlleleFrequency,\n parsePvalToLog,\n};\n","/**\n * Parse BED-family files, which have 3-12 columns\n * https://genome.ucsc.edu/FAQ/FAQformat.html#format1\n */\n\nimport {normalizeChr} from './utils';\n\n/**\n * @private\n */\nfunction _bedMissing(value) {\n // BED files specify . as the missing/ null value character\n if (value === null || value === undefined || value === '.') {\n return null;\n }\n return value;\n}\n\n/**\n * @private\n */\nfunction _hasNum(value) {\n // Return a number, or null if value marked as missing\n value = _bedMissing(value);\n return value ? +value : null;\n}\n\n/**\n * Parse a BED file, according to the widely used UCSC (quasi-)specification\n *\n * NOTE: This original version is aimed at tabix region queries, and carries an implicit assumption that data is the\n * only thing that will be parsed. It makes no attempt to identify or handle header rows / metadata fields.\n *\n * @function\n * @alias module:ext/lz-parsers~makeBed12Parser\n * @param {object} options\n * @param {Boolean} options.normalize Whether to normalize the output to the format expected by LocusZoom (eg type coercion\n * for numbers, removing chr chromosome prefixes, and using 1-based and inclusive coordinates instead of 0-based disjoint intervals)\n * @return function A configured parser function that runs on one line of text from an input file\n */\nfunction makeBed12Parser({normalize = true} = {}) {\n /*\n * @param {String} line The line of text to be parsed\n */\n return (line) => {\n const tokens = line.trim().split('\\t');\n // The BED file format has 12 standardized columns. 3 are required and 9 are optional. At present, we will not\n // attempt to parse any remaining tokens, or nonstandard files that reuse columns with a different meaning.\n // https://en.wikipedia.org/wiki/BED_(file_format)\n let [\n chrom,\n chromStart,\n chromEnd,\n name,\n score,\n strand,\n thickStart,\n thickEnd,\n itemRgb,\n blockCount,\n blockSizes,\n blockStarts,\n ] = tokens;\n\n if (!(chrom && chromStart && chromEnd)) {\n throw new Error('Sample data must provide all required BED columns');\n }\n\n strand = _bedMissing(strand);\n\n if (normalize) {\n // Mandatory fields\n chrom = normalizeChr(chrom);\n chromStart = +chromStart + 1; // BED is 0 based start, but LZ plots start at 1\n chromEnd = +chromEnd; // 0-based positions, intervals exclude end position\n\n // Optional fields, require checking for blanks\n score = _hasNum(score);\n thickStart = _hasNum(thickStart);\n thickEnd = _hasNum(thickEnd);\n\n itemRgb = _bedMissing(itemRgb);\n\n // LocusZoom doesn't use these fields for rendering. Parsing below is theoretical/best-effort.\n blockCount = _hasNum(blockCount);\n\n blockSizes = _bedMissing(blockSizes);\n blockSizes = !blockSizes ? null : blockSizes.replace(/,$/, '').split(',').map((value) => +value); // Comma separated list of sizes -> array of integers\n\n blockStarts = _bedMissing(blockStarts);\n blockStarts = !blockStarts ? null : blockStarts.replace(/,$/, '').split(',').map((value) => +value + 1); // Comma separated list of sizes -> array of integers (start positions)\n\n if (blockSizes && blockStarts && blockCount && (blockSizes.length !== blockCount || blockStarts.length !== blockCount)) {\n throw new Error('Block size and start information should provide the same number of items as in blockCount');\n }\n }\n return {\n chrom,\n chromStart,\n chromEnd,\n name,\n score,\n strand,\n thickStart,\n thickEnd,\n itemRgb,\n blockCount,\n blockSizes,\n blockStarts,\n };\n };\n}\n\nexport { makeBed12Parser };\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Sniffers: auto detect file format and parsing options for GWAS files.\n */\n\nimport { parseMarker } from '../../../helpers/parse';\nimport { MISSING_VALUES, parsePvalToLog, _missingToNull } from '../utils';\n\n/**\n * @private\n */\nfunction isNumeric(val) {\n // Check whether an unparsed string is a numeric value\"\n if (MISSING_VALUES.has(val)) {\n return true;\n }\n return !Number.isNaN(+val);\n}\n\nfunction isHeader(row, { comment_char = '#', delimiter = '\\t' } = {}) {\n // This assumes two basic rules: the line is not a comment, and gwas data is more likely\n // to be numeric than headers\n return row.startsWith(comment_char) || row.split(delimiter).every((item) => !isNumeric(item));\n}\n\n/**\n * Compute the levenshtein distance between two strings. Useful for finding the single best column\n * name that matches a given rule.\n * @private\n */\nfunction levenshtein(a, b) { // https://github.com/trekhleb/javascript-algorithms\n // Create empty edit distance matrix for all possible modifications of\n // substrings of a to substrings of b.\n const distanceMatrix = Array(b.length + 1)\n .fill(null)\n .map(() => Array(a.length + 1)\n .fill(null));\n\n // Fill the first row of the matrix.\n // If this is first row then we're transforming empty string to a.\n // In this case the number of transformations equals to size of a substring.\n for (let i = 0; i <= a.length; i += 1) {\n distanceMatrix[0][i] = i;\n }\n\n // Fill the first column of the matrix.\n // If this is first column then we're transforming empty string to b.\n // In this case the number of transformations equals to size of b substring.\n for (let j = 0; j <= b.length; j += 1) {\n distanceMatrix[j][0] = j;\n }\n\n for (let j = 1; j <= b.length; j += 1) {\n for (let i = 1; i <= a.length; i += 1) {\n const indicator = a[i - 1] === b[j - 1] ? 0 : 1;\n distanceMatrix[j][i] = Math.min(\n distanceMatrix[j][i - 1] + 1, // deletion\n distanceMatrix[j - 1][i] + 1, // insertion\n distanceMatrix[j - 1][i - 1] + indicator, // substitution\n );\n }\n }\n return distanceMatrix[b.length][a.length];\n}\n\n/**\n * Return the index of the first column name that meets acceptance criteria\n * @private\n * @param {String[]} column_synonyms\n * @param {String[]}header_names\n * @param {Number} threshold Tolerance for fuzzy matching (# edits)\n * @return {Number|null} Index of the best matching column, or null if no match possible\n */\nfunction findColumn(column_synonyms, header_names, threshold = 2) {\n // Find the column name that best matches\n let best_score = threshold + 1;\n let best_match = null;\n for (let i = 0; i < header_names.length; i++) {\n const header = header_names[i];\n if (header === null) {\n // If header is empty, don't consider it for a match\n // Nulling a header provides a way to exclude something from future searching\n continue; // eslint-disable-line no-continue\n }\n const score = Math.min(...column_synonyms.map((s) => levenshtein(header, s)));\n if (score < best_score) {\n best_score = score;\n best_match = i;\n }\n }\n return best_match;\n}\n\n\n/**\n * Return parser configuration for pvalues\n *\n * Returns 1-based column indices, for compatibility with parsers\n * @private\n * @param header_row\n * @param data_rows\n * @returns {{}}\n */\nfunction getPvalColumn(header_row, data_rows) {\n // TODO: Allow overrides\n const LOGPVALUE_FIELDS = ['neg_log_pvalue', 'log_pvalue', 'log_pval', 'logpvalue'];\n const PVALUE_FIELDS = ['pvalue', 'p.value', 'p-value', 'pval', 'p_score', 'p', 'p_value'];\n\n let ps;\n const validateP = (col, data, is_log) => { // Validate pvalues\n const cleaned_vals = _missingToNull(data.map((row) => row[col]));\n try {\n ps = cleaned_vals.map((p) => parsePvalToLog(p, is_log));\n } catch (e) {\n return false;\n }\n return ps.every((val) => !Number.isNaN(val));\n };\n\n const log_p_col = findColumn(LOGPVALUE_FIELDS, header_row);\n const p_col = findColumn(PVALUE_FIELDS, header_row);\n\n if (log_p_col !== null && validateP(log_p_col, data_rows, true)) {\n return {\n pvalue_col: log_p_col + 1,\n is_neg_log_pvalue: true,\n };\n }\n if (p_col && validateP(p_col, data_rows, false)) {\n return {\n pvalue_col: p_col + 1,\n is_neg_log_pvalue: false,\n };\n }\n // Could not auto-determine an appropriate pvalue column\n return null;\n}\n\n/**\n * @private\n */\nfunction getChromPosRefAltColumns(header_row, data_rows) {\n // Returns 1-based column indices, for compatibility with parsers\n // Get from either a marker, or 4 separate columns\n const MARKER_FIELDS = ['snpid', 'marker', 'markerid', 'snpmarker', 'chr:position'];\n const CHR_FIELDS = ['chrom', 'chr', 'chromosome'];\n const POS_FIELDS = ['position', 'pos', 'begin', 'beg', 'bp', 'end', 'ps', 'base_pair_location'];\n\n // TODO: How to handle orienting ref vs effect?\n // Order matters: consider ambiguous field names for ref before alt\n const REF_FIELDS = ['A1', 'ref', 'reference', 'allele0', 'allele1'];\n const ALT_FIELDS = ['A2', 'alt', 'alternate', 'allele1', 'allele2'];\n\n const first_row = data_rows[0];\n let marker_col = findColumn(MARKER_FIELDS, header_row);\n if (marker_col !== null && parseMarker(first_row[marker_col], true)) {\n marker_col += 1;\n return { marker_col };\n }\n\n // If single columns were incomplete, attempt to auto detect 4 separate columns. All 4 must\n // be found for this function to report a match.\n const headers_marked = header_row.slice();\n const find = [\n ['chrom_col', CHR_FIELDS, true],\n ['pos_col', POS_FIELDS, true],\n ['ref_col', REF_FIELDS, false],\n ['alt_col', ALT_FIELDS, false],\n ];\n const config = {};\n for (let i = 0; i < find.length; i++) {\n const [col_name, choices, is_required] = find[i];\n const col = findColumn(choices, headers_marked);\n if (col === null && is_required) {\n return null;\n }\n if (col !== null) {\n config[col_name] = col + 1;\n // Once a column has been assigned, remove it from consideration\n headers_marked[col] = null;\n }\n }\n return config;\n}\n\n/**\n * Identify which columns contain effect size (beta) and stderr of the effect size\n * @private\n * @param {String[]} header_names\n * @param {Array[]} data_rows\n * @returns {{}}\n */\nfunction getEffectSizeColumns(header_names, data_rows) {\n const BETA_FIELDS = ['beta', 'effect_size', 'alt_effsize', 'effect'];\n const STDERR_BETA_FIELDS = ['stderr_beta', 'stderr', 'sebeta', 'effect_size_sd', 'se', 'standard_error'];\n\n function validate_numeric(col, data) {\n const cleaned_vals = _missingToNull(data.map((row) => row[col]));\n let nums;\n try {\n nums = cleaned_vals.filter((val) => val !== null).map((val) => +val);\n } catch (e) {\n return false;\n }\n return nums.every((val) => !Number.isNaN(val));\n }\n\n const beta_col = findColumn(BETA_FIELDS, header_names, 0);\n const stderr_beta_col = findColumn(STDERR_BETA_FIELDS, header_names, 0);\n\n const ret = {};\n if (beta_col !== null && validate_numeric(beta_col, data_rows)) {\n ret.beta_col = beta_col + 1;\n }\n if (stderr_beta_col !== null && validate_numeric(stderr_beta_col, data_rows)) {\n ret.stderr_beta_col = stderr_beta_col + 1;\n }\n return ret;\n}\n\n/**\n * Attempt to guess the correct parser settings given a set of header rows and a set of data rows\n * @private\n * @param {String[]} header_row\n * @param {String[][]} data_rows\n * @param {int} offset Used to convert between 0 and 1-based indexing.\n * @return {Object|null} Returns null if a complete configuration could not suggested.\n */\nfunction guessGWAS(header_row, data_rows, offset = 1) {\n // 1. Find a specific set of info: marker OR chr/pos/ref/alt ; pvalue OR log_pvalue\n // 2. Validate that we will be able to parse the required info: fields present and make sense\n // 3. Based on the field names selected, attempt to infer meaning: verify whether log is used,\n // and check ref/alt vs effect/noneffect\n // 4. Return a parser config object if all tests pass, OR null.\n\n // Normalize case and remove leading comment marks from line for easier comparison\n const headers = header_row.map((item) => (item ? item.toLowerCase() : item));\n headers[0].replace('/^#+/', '');\n // Lists of fields are drawn from Encore (AssocResultReader) and Pheweb (conf_utils.py)\n const pval_config = getPvalColumn(headers, data_rows, offset);\n if (!pval_config) {\n return null;\n }\n headers[pval_config.pvalue_col - 1] = null; // Remove this column from consideration\n const position_config = getChromPosRefAltColumns(headers, data_rows);\n if (!position_config) {\n return null;\n }\n // Remove the position config from consideration for future matches\n Object.keys(position_config).forEach((key) => {\n headers[position_config[key]] = null;\n });\n\n const beta_config = getEffectSizeColumns(headers, data_rows);\n\n if (pval_config && position_config) {\n return Object.assign({}, pval_config, position_config, beta_config || {});\n }\n return null;\n}\n\nexport {\n // Public members\n guessGWAS,\n // Symbols exported for testing\n isHeader as _isHeader,\n getPvalColumn as _getPvalColumn,\n findColumn as _findColumn,\n levenshtein as _levenshtein,\n};\n","/**\n * Optional LocusZoom extension: must be included separately, and after LocusZoom has been loaded\n *\n * This plugin exports helper function, as well as a few optional extra helpers for rendering the plot. The GWAS parsers can be used without registering the plugin.\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n * ```\n * \n * ```\n *\n * To use with ES6 modules, import the helper functions and use them with your layout:\n *\n * ```\n * import { install, makeGWASParser, makeBed12Parser, makePlinkLDParser } from 'locuszoom/esm/ext/lz-parsers';\n * LocusZoom.use(install);\n * ```\n *\n * ### Features provided\n * * {@link module:LocusZoom_Adapters~UserTabixLD} (if the {@link module:ext/lz-tabix-source} extension is loaded first)\n *\n * @module ext/lz-parsers\n */\n\nimport { makeBed12Parser } from './bed';\nimport { makeGWASParser } from './gwas/parsers';\nimport { guessGWAS } from './gwas/sniffers';\nimport { makePlinkLdParser } from './ld';\n\n\n// Most of this plugin consists of standalone functions. But we can add a few simple custom classes to the registry that help to use parsed output\nfunction install(LocusZoom) {\n if (LocusZoom.Adapters.has('TabixUrlSource')) {\n // Custom Tabix adapter depends on another extension being loaded first\n const TabixUrlSource = LocusZoom.Adapters.get('TabixUrlSource');\n const LDServer = LocusZoom.Adapters.get('LDServer');\n\n /**\n * Load user-provided LD from a tabix file, and filter the returned set of records based on a reference variant. (will attempt to choose a reference variant based on the most significant association variant, if no state.ldrefvar is specified)\n * @public\n * @alias module:LocusZoom_Adapters~UserTabixLD\n * @extends module:LocusZoom_Adapters~TabixUrlSource\n * @see {@link module:ext/lz-tabix-source} for required extension and installation instructions\n * @see {@link module:ext/lz-parsers} for required extension and installation instructions\n */\n class UserTabixLD extends TabixUrlSource {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n base._skip_request = true;\n return base;\n }\n\n // NOTE: Reuses a method from another adapter to mix in functionality\n base.ld_refvar = LDServer.prototype.__find_ld_refvar(state, assoc_data);\n return base;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n return Promise.resolve([]);\n }\n return super._performRequest(options);\n }\n\n _annotateRecords(records, options) {\n // A single PLINK LD file could contain several reference variants (SNP_A) in the same region.\n // Only show LD relative to the user-selected refvar in this plot.\n return records.filter((item) => item['variant1'] === options.ld_refvar);\n }\n }\n\n\n LocusZoom.Adapters.add('UserTabixLD', UserTabixLD);\n }\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n// Support UMD (single symbol export)\nconst all = { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser };\n\nexport default all;\n\nexport { install, makeBed12Parser, makeGWASParser, guessGWAS, makePlinkLdParser };\n","import {parseMarker} from '../../../helpers/parse';\n\nimport {\n MISSING_VALUES,\n has,\n parseAlleleFrequency,\n parsePvalToLog, normalizeChr,\n} from '../utils';\n\n\n/**\n * Specify how to parse a GWAS file, given certain column information.\n * Outputs an object with fields in portal API format.\n *\n * All column options must be provided as 1-indexed column IDs (human-friendly argument values)\n * @function\n * @alias module:ext/lz-parsers~makeGWASParser\n * @param options\n * @param [options.marker_col] A single identifier that specifies all of chrom, pos, ref, and alt as a single string field. Eg 1:23_A/C\n * @param [options.chrom_col] Chromosome\n * @param [options.pos_col] Position\n * @param [options.ref_col] Reference allele (relative to human reference genome, eg GRCh37 or 38).\n * @param [options.alt_col] Alt allele. Some programs specify generic A1/A2 instead; it is the job of the user to identify which columns of this GWAS are ref and alt.\n * @param [options.rsid_col] rsID\n * @param options.pvalue_col p-value (or -log10p)\n * @param [options.beta_col]\n * @param [options.stderr_beta_col]\n * @param [options.allele_freq_col] Specify allele frequencies directly\n * @param [options.allele_count_col] Specify allele frequencies in terms of count and n_samples\n * @param [options.n_samples_col]\n * @param [options.is_alt_effect=true] Some programs specify beta and frequency information in terms of ref, others alt. Identify effect allele to orient values to the correct allele.\n * @param [options.is_neg_log_pvalue=false]\n * @param [options.delimiter='\\t'] Since this parser is usually used with tabix data, this is rarely changed (tabix does not accept other delimiters)\n * @return {function(string)} A parser function that can be called on each line of text with the provided options\n */\nfunction makeGWASParser(\n {\n // Required fields\n marker_col, // Identify the variant: marker OR chrom/pos/ref/alt\n chrom_col,\n pos_col,\n ref_col,\n alt_col,\n pvalue_col, // pvalue (or log_pvalue; see options below)\n // Optional fields\n is_neg_log_pvalue = false,\n rsid_col,\n beta_col,\n stderr_beta_col,\n allele_freq_col, // Frequency: given directly, OR in terms of counts\n allele_count_col,\n n_samples_col,\n is_alt_effect = true, // whether effect allele is oriented towards alt. We don't support files like METAL, where ref/alt may switch places per line of the file\n delimiter = '\\t',\n },\n) {\n // Column IDs should be 1-indexed (human friendly)\n if (has(marker_col) && has(chrom_col) && has(pos_col)) {\n throw new Error('Must specify either marker OR chr + pos');\n }\n if (!(has(marker_col) || (has(chrom_col) && has(pos_col)))) {\n throw new Error('Must specify how to locate marker');\n }\n\n if (has(allele_count_col) && has(allele_freq_col)) {\n throw new Error('Allele count and frequency options are mutually exclusive');\n }\n if (has(allele_count_col) && !has(n_samples_col)) {\n throw new Error('To calculate allele frequency from counts, you must also provide n_samples');\n }\n\n\n return (line) => {\n const fields = line.split(delimiter);\n let chr;\n let pos;\n let ref;\n let alt;\n let rsid = null;\n\n let freq;\n let beta = null;\n let stderr_beta = null;\n let alt_allele_freq = null;\n let allele_count;\n let n_samples;\n\n if (has(marker_col)) {\n [chr, pos, ref, alt] = parseMarker(fields[marker_col - 1], false);\n } else if (has(chrom_col) && has(pos_col)) {\n chr = fields[chrom_col - 1];\n pos = fields[pos_col - 1];\n } else {\n throw new Error('Must specify all fields required to identify the variant');\n }\n\n chr = normalizeChr(chr);\n if (chr.startsWith('RS')) {\n throw new Error(`Invalid chromosome specified: value \"${chr}\" is an rsID`);\n }\n\n if (has(ref_col)) {\n ref = fields[ref_col - 1];\n }\n\n if (has(alt_col)) {\n alt = fields[alt_col - 1];\n }\n\n if (has(rsid_col)) {\n rsid = fields[rsid_col - 1];\n }\n\n if (MISSING_VALUES.has(ref)) {\n ref = null;\n }\n if (MISSING_VALUES.has(alt)) {\n alt = null;\n }\n\n if (MISSING_VALUES.has(rsid)) {\n rsid = null;\n } else if (rsid) {\n rsid = rsid.toLowerCase();\n if (!rsid.startsWith('rs')) {\n rsid = `rs${rsid}`;\n }\n }\n\n const log_pval = parsePvalToLog(fields[pvalue_col - 1], is_neg_log_pvalue);\n ref = ref || null;\n alt = alt || null;\n\n if (has(allele_freq_col)) {\n freq = fields[allele_freq_col - 1];\n }\n if (has(allele_count_col)) {\n allele_count = fields[allele_count_col - 1];\n n_samples = fields[n_samples_col - 1];\n }\n\n if (has(beta_col)) {\n beta = fields[beta_col - 1];\n beta = MISSING_VALUES.has(beta) ? null : (+beta);\n }\n\n if (has(stderr_beta_col)) {\n stderr_beta = fields[stderr_beta_col - 1];\n stderr_beta = MISSING_VALUES.has(stderr_beta) ? null : (+stderr_beta);\n }\n\n if (allele_freq_col || allele_count_col) {\n alt_allele_freq = parseAlleleFrequency({\n freq,\n allele_count,\n n_samples,\n is_alt_effect,\n });\n }\n const ref_alt = (ref && alt) ? `_${ref}/${alt}` : '';\n return {\n chromosome: chr,\n position: +pos,\n ref_allele: ref ? ref.toUpperCase() : null,\n alt_allele: alt ? alt.toUpperCase() : null,\n variant: `${chr}:${pos}${ref_alt}`,\n rsid,\n log_pvalue: log_pval,\n beta,\n stderr_beta,\n alt_allele_freq,\n };\n };\n}\n\n\nexport { makeGWASParser };\n","/**\n * Parsers for custom user-specified LD\n */\n\nimport {normalizeChr} from './utils';\nimport {normalizeMarker} from '../../helpers/parse';\n\n/**\n * Parse the output of plink v1.9 R2 calculations relative to one (or a few) target SNPs.\n * See: https://www.cog-genomics.org/plink/1.9/ld and\n * reformatting commands at https://www.cog-genomics.org/plink/1.9/other\n * @function\n * @alias module:ext/lz-parsers~makePlinkLdParser\n * @param {object} options\n * @param {boolean} [options.normalize=true] Normalize fields to expected datatypes and format; if false, returns raw strings as given in the file\n * @return {function} A configured parser function that runs on one line of text from an input file\n */\nfunction makePlinkLdParser({normalize = true} = {}) {\n return (line) => {\n // Sample headers are below: SNP_A and SNP_B are based on ID column of the VCF\n // CHR_A BP_A SNP_A CHR_B BP_B SNP_B R2\n let [chromosome1, position1, variant1, chromosome2, position2, variant2, correlation] = line.trim().split('\\t');\n if (normalize) {\n chromosome1 = normalizeChr(chromosome1);\n chromosome2 = normalizeChr(chromosome2);\n variant1 = normalizeMarker(variant1);\n variant2 = normalizeMarker(variant2);\n position1 = +position1;\n position2 = +position2;\n correlation = +correlation;\n }\n\n return {chromosome1, position1, variant1, chromosome2, position2, variant2, correlation};\n };\n}\n\nexport { makePlinkLdParser };\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/ext/lz-tabix-source.min.js b/dist/ext/lz-tabix-source.min.js index d5a9c374..7700b1d2 100644 --- a/dist/ext/lz-tabix-source.min.js +++ b/dist/ext/lz-tabix-source.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.2 */ +/*! Locuszoom 0.14.0-beta.3 */ var LzTabix;(()=>{var t={398:t=>{var i=15,e=-2,n=-3,r=-5,s=13,a=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],o=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],h=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],l=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],f=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],u=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],d=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];function _(){}function c(){this.was=[0]}_.prototype.inflateInit=function(t,i){return t||(t=15),i&&(i=!1),this.istate=new c,this.istate.inflateInit(this,i?-t:t)},_.prototype.inflate=function(t){return null==this.istate?e:this.istate.inflate(this,t)},_.prototype.inflateEnd=function(){if(null==this.istate)return e;var t=istate.inflateEnd(this);return this.istate=null,t},_.prototype.inflateSync=function(){return istate.inflateSync(this)},_.prototype.inflateSetDictionary=function(t,i){return istate.inflateSetDictionary(this,t,i)},c.prototype.inflateReset=function(t){return null==t||null==t.istate?e:(t.total_in=t.total_out=0,t.msg=null,t.istate.mode=0!=t.istate.nowrap?7:0,t.istate.blocks.reset(t,null),0)},c.prototype.inflateEnd=function(t){return null!=this.blocks&&this.blocks.free(t),this.blocks=null,0},c.prototype.inflateInit=function(t,i){return t.msg=null,this.blocks=null,nowrap=0,i<0&&(i=-i,nowrap=1),i<8||i>15?(this.inflateEnd(t),e):(this.wbits=i,t.istate.blocks=new v(t,0!=t.istate.nowrap?null:this,1<>4)>t.istate.wbits){t.istate.mode=s,t.msg="invalid window size",t.istate.marker=5;break}t.istate.mode=1;case 1:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,o=255&t.next_in[t.next_in_index++],((t.istate.method<<8)+o)%31!=0){t.istate.mode=s,t.msg="incorrect header check",t.istate.marker=5;break}if(0==(32&o)){t.istate.mode=7;break}t.istate.mode=2;case 2:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=3;case 3:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=4;case 4:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=5;case 5:return 0==t.avail_in?a:(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.adler=t.istate.need,t.istate.mode=6,2);case 6:return t.istate.mode=s,t.msg="need dictionary",t.istate.marker=0,e;case 7:if((a=t.istate.blocks.proc(t,a))==n){t.istate.mode=s,t.istate.marker=0;break}if(0==a&&(a=i),1!=a)return a;if(a=i,t.istate.blocks.reset(t,t.istate.was),0!=t.istate.nowrap){t.istate.mode=12;break}t.istate.mode=8;case 8:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=9;case 9:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=10;case 10:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=11;case 11:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.istate.was[0]!=t.istate.need){t.istate.mode=s,t.msg="incorrect data check",t.istate.marker=5;break}t.istate.mode=12;case 12:return 1;case s:return n;default:return e}},c.prototype.inflateSetDictionary=function(t,i,r){var s=0,a=r;return null==t||null==t.istate||6!=t.istate.mode?e:t._adler.adler32(1,i,0,r)!=t.adler?n:(t.adler=t._adler.adler32(0,null,0,0),a>=1<>>1){case 0:o>>>=3,o>>>=r=7&(h-=3),h-=r,this.mode=1;break;case 1:w(v=new Int32Array(1),p=new Int32Array(1),m=[],y=[],t),this.codes.init(v[0],p[0],m[0],0,y[0],0,t),o>>>=3,h-=3,this.mode=6;break;case 2:o>>>=3,h-=3,this.mode=3;break;case 3:return o>>>=3,h-=3,this.mode=s,t.msg="invalid block type",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i)}break;case 1:for(;h<32;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>16&65535)!=(65535&o))return this.mode=s,t.msg="invalid stored block lengths",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.left=65535&o,o=h=0,this.mode=0!=this.left?2:0!=this.last?7:0;break;case 2:if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);if(0==d&&(u==end&&0!=read&&(d=(u=0)f&&(r=f),r>d&&(r=d),g(t.next_in,l,this.window,u,r),l+=r,f-=r,u+=r,d-=r,0!=(this.left-=r))break;this.mode=0!=this.last?7:0;break;case 3:for(;h<14;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<29||(r>>5&31)>29)return this.mode=9,t.msg="too many length or distance symbols",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);if(r=258+(31&r)+(r>>5&31),null==this.blens||this.blens.length>>=14,h-=14,this.index=0,mode=4;case 4:for(;this.index<4+(this.table>>>10);){for(;h<3;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>=3,h-=3}for(;this.index<19;)this.blens[b[this.index++]]=0;if(this.bb[0]=7,0!=(r=this.inftree.inflate_trees_bits(this.blens,this.bb,this.tb,this.hufts,t)))return(i=r)==n&&(this.blens=null,this.mode=9),this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);this.index=0,this.mode=5;case 5:for(;r=this.table,this.index<258+(31&r)+(r>>5&31);){var c,x;for(r=this.bb[0];h>>=r,h-=r,this.blens[this.index++]=x;else{for(_=18==x?7:x-14,c=18==x?11:3;h>>=r)&a[_],o>>>=_,h-=_,(_=this.index)+c>258+(31&(r=this.table))+(r>>5&31)||16==x&&_<1)return this.blens=null,this.mode=9,t.msg="invalid bit length repeat",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);x=16==x?this.blens[_-1]:0;do{this.blens[_++]=x}while(0!=--c);this.index=_}}this.tb[0]=-1;var v=new Int32Array(1),p=new Int32Array(1),m=new Int32Array(1),y=new Int32Array(1);if(v[0]=9,p[0]=6,r=this.table,0!=(r=this.inftree.inflate_trees_dynamic(257+(31&r),1+(r>>5&31),this.blens,v,p,m,y,this.hufts,t)))return r==n&&(this.blens=null,this.mode=s),i=r,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.codes.init(v[0],p[0],this.hufts,m[0],this.hufts,y[0],t),this.mode=6;case 6:if(this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,1!=(i=this.codes.proc(this,t,i)))return this.inflate_flush(t,i);if(i=0,this.codes.free(t),l=t.next_in_index,f=t.avail_in,o=this.bitb,h=this.bitk,d=(u=this.write)t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,(s+=e)==this.end&&(s=0,this.write==this.end&&(this.write=0),(e=this.write-s)>t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,s+=e),t.next_out_index=n,this.read=s,i};function p(){}function m(){}function w(t,i,e,n,r){return t[0]=9,i[0]=5,e[0]=o,n[0]=h,0}p.prototype.init=function(t,i,e,n,r,s,a){this.mode=0,this.lbits=t,this.dbits=i,this.ltree=e,this.ltree_index=n,this.dtree=r,this.dtree_index=s,this.tree=null},p.prototype.proc=function(t,i,r){var s,o,h,l,f,u,d,_=0,c=0,x=0;for(x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)=258&&l>=10&&(t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,r=this.inflate_fast(this.lbits,this.dbits,this.ltree,this.ltree_index,this.dtree,this.dtree_index,t,i),x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)>>=this.tree[o+1],c-=this.tree[o+1],0==(h=this.tree[o])){this.lit=this.tree[o+2],this.mode=6;break}if(0!=(16&h)){this.get=15&h,this.len=this.tree[o+2],this.mode=2;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}if(0!=(32&h)){this.mode=7;break}return this.mode=9,i.msg="invalid literal/length code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 2:for(s=this.get;c>=s,c-=s,this.need=this.dbits,this.tree=this.dtree,this.tree_index=this.dtree_index,this.mode=3;case 3:for(s=this.need;c>=this.tree[o+1],c-=this.tree[o+1],0!=(16&(h=this.tree[o]))){this.get=15&h,this.dist=this.tree[o+2],this.mode=4;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}return this.mode=9,i.msg="invalid distance code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 4:for(s=this.get;c>=s,c-=s,this.mode=5;case 5:for(d=f-this.dist;d<0;)d+=t.end;for(;0!=this.len;){if(0==u&&(f==t.end&&0!=t.read&&(u=(f=0)7&&(c-=8,l++,x--),t.write=f,r=t.inflate_flush(i,r),u=(f=t.write)>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15,k=u[S+2]+(c&a[_]),c>>=_,x-=_;x<15;)v--,c|=(255&l.next_in[b++])<>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15;x<_;)v--,c|=(255&l.next_in[b++])<>=_,x-=_,m-=k,p>=A)C=p-A,h.window[p++]=h.window[C++],h.window[p++]=h.window[C++],k-=2;else{C=p-A;do{C+=h.end}while(C<0);if(k>(_=h.end-C)){if(k-=_,p-C>0&&_>p-C)do{h.window[p++]=h.window[C++]}while(0!=--_);else g(h.window,C,h.window,p,_),p+=_,C+=_,_=0;C=0}}do{h.window[p++]=h.window[C++]}while(0!=--k);break}if(0!=(64&_))return l.msg="invalid distance code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n;f+=u[S+2],_=u[S=3*(d+(f+=c&a[_]))]}break}if(0!=(64&_))return 0!=(32&_)?(v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,1):(l.msg="invalid literal/length code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n);if(f+=u[S+2],0==(_=u[S=3*(d+(f+=c&a[_]))])){c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--;break}}else c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--}while(m>=258&&v>=10);return v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,0},m.prototype.huft_build=function(t,e,s,a,o,h,l,f,u,d,_){var c,x,b,v,p,m,w,y,k,A,C,S,R,I,L;A=0,p=s;do{this.c[t[e+A]]++,A++,p--}while(0!=p);if(this.c[0]==s)return l[0]=-1,f[0]=0,0;for(y=f[0],m=1;m<=i&&0==this.c[m];m++);for(w=m,yp&&(y=p),f[0]=y,I=1<S+y;){if(v++,L=(L=b-(S+=y))>y?y:L,(x=1<<(m=w-S))>c+1&&(x-=c+1,R=w,m1440)return n;this.u[v]=C=this.hn[0],this.hn[0]+=L,0!=v?(this.x[v]=p,this.r[0]=m,this.r[1]=y,m=p>>>S-y,this.r[2]=C-this.u[v-1]-m,g(this.r,0,u,3*(this.u[v-1]+m),3)):l[0]=C}for(this.r[1]=w-S,A>=s?this.r[0]=192:_[A]>>S;m>>=1)p^=m;for(p^=m,k=(1<257?(x==n?c.msg="oversubscribed distance tree":x==r?(c.msg="incomplete distance tree",x=n):-4!=x&&(c.msg="empty distance tree with lengths",x=n),x):0)},m.prototype.initWorkArea=function(t){null==this.hn&&(this.hn=new Int32Array(1),this.v=new Int32Array(t),this.c=new Int32Array(16),this.r=new Int32Array(3),this.u=new Int32Array(i),this.x=new Int32Array(16)),this.v.length100?k(new Uint8Array(t.buffer,t.byteOffset+i,r),e,n):function(t,i,e,n,r){for(var s=0;s{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},e.o=(t,i)=>Object.prototype.hasOwnProperty.call(t,i);var n={};(()=>{"use strict";e.d(n,{default:()=>q});function t(t){return r(i(s(t)))}function i(t){return o(h(a(t),8*t.length))}function r(t){for(var i="",e=t.length,n=0;n8*t.length?i+="":i+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(r>>>6*(3-s)&63);return i}function s(t){for(var i,e,n="",r=-1;++r>>6&31,128|63&i):i<=65535?n+=String.fromCharCode(224|i>>>12&15,128|i>>>6&63,128|63&i):i<=2097151&&(n+=String.fromCharCode(240|i>>>18&7,128|i>>>12&63,128|i>>>6&63,128|63&i));return n}function a(t){for(var i=Array(t.length>>2),e=0;e>5]|=(255&t.charCodeAt(e/8))<<24-e%32;return i}function o(t){for(var i="",e=0;e<32*t.length;e+=8)i+=String.fromCharCode(t[e>>5]>>>24-e%32&255);return i}function h(t,i){t[i>>5]|=128<<24-i%32,t[15+(i+64>>9<<4)]=i;for(var e=Array(80),n=1732584193,r=-271733879,s=-1732584194,a=271733878,o=-1009589776,h=0;h>16)+(i>>16)+(e>>16)<<16|65535&e}function d(t,i){return t<>>32-i}function _(t){this.value=t,this.listeners=[]}function c(){this.queue=[]}_.prototype.addListener=function(t){this.listeners.push(t)},_.prototype.addListenerAndFire=function(t){this.listeners.push(t),t(this.value)},_.prototype.removeListener=function(t){var i,e;i=this.listeners,(e=function(t,i){if(!t)return-1;for(var e=0;e=0&&i.splice(e,1)},_.prototype.get=function(){return this.value},_.prototype.set=function(t){this.value=t;for(var i=0;i=0&&navigator.userAgent.indexOf("Chrome")<0;function m(t){if(!t)return null;for(var i=new Uint8Array(t.length),e=0;e1e8)throw"Monster fetch!";r.setRequestHeader("Range","bytes="+e.start+"-"+e.end),e.end-e.start+1}r.onreadystatechange=function(){if(4==r.readyState)return 200==r.status||206==r.status?i(r.responseText):i(null)},e.opts.credentials&&(r.withCredentials=!0),r.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))},b.prototype.salted=function(){var t=function(t){var i={};for(var e in t)i[e]=t[e];return i}(this.opts);return t.salt=!0,new b(this.url,this.start,this.end,t)},b.prototype.getURL=function(){return this.opts.resolver?this.opts.resolver(this.url).then((function(t){return"string"==typeof t?t:t.url})):Promise.resolve(this.url)},b.prototype.fetch=function(i,e){var n=this,r=(e=e||{}).attempt||1,s=e.truncatedLength;if(r>3)return i(null);this.getURL().then((function(a){try{var o;e.timeout&&!n.opts.credentials&&(o=setTimeout((function(){return console.log("timing out "+a),l.abort(),i(null,"Timeout")}),e.timeout));var h,l=new XMLHttpRequest;if((p||n.opts.salt)&&a.indexOf("?")<0&&(a=a+"?salt="+t(Date.now()+","+ ++v)),l.open("GET",a,!0),l.overrideMimeType("text/plain; charset=x-user-defined"),n.end){if(n.end-n.start>1e8)throw"Monster fetch!";l.setRequestHeader("Range","bytes="+n.start+"-"+n.end),h=n.end-n.start+1}l.responseType="arraybuffer",l.onreadystatechange=function(){if(4==l.readyState){if(o&&clearTimeout(o),200==l.status||206==l.status){if(l.response){var t=l.response.byteLength;return!h||h==t||s&&t==s?i(l.response):n.fetch(i,{attempt:r+1,truncatedLength:t})}if(l.mozResponseArrayBuffer)return i(l.mozResponseArrayBuffer);var e=l.responseText;return!h||h==e.length||s&&e.length==s?i(m(l.responseText)):n.fetch(i,{attempt:r+1,truncatedLength:e.length})}return n.fetch(i,{attempt:r+1})}},n.opts.credentials&&(l.withCredentials=!0),l.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))};var w=new ArrayBuffer(8);new Uint8Array(w),new Float32Array(w);function y(t,i){return t[i+3]<<24|t[i+2]<<16|t[i+1]<<8|t[i]}var g=e(398);function k(t,i){this.block=t,this.offset=i}function A(t,i,e){var n=4294967296*(255&t[i+6])+16777216*(255&t[i+5])+65536*(255&t[i+4])+256*(255&t[i+3])+(255&t[i+2]),r=t[i+1]<<8|t[i];return 0!=n||0!=r||e?new k(n,r):null}function C(t,i){i=Math.min(i||1,t.byteLength-50);for(var e=[],n=[0],r=0;n[0]n._max&&(n._max=t._max):(e.push(n),n=t)})),e.push(n),this._ranges=e}function L(t,i){return t._mini._min?1:t._maxt._max?1:0}R.prototype.min=function(){return this._min},R.prototype.max=function(){return this._max},R.prototype.contains=function(t){return t>=this._min&&t<=this._max},R.prototype.isContiguous=function(){return!0},R.prototype.ranges=function(){return[this]},R.prototype._pushRanges=function(t){t.push(this)},R.prototype.toString=function(){return"["+this._min+"-"+this._max+"]"},I.prototype.min=function(){return this._ranges[0].min()},I.prototype.max=function(){return this._ranges[this._ranges.length-1].max()},I.prototype.lower_bound=function(t){var i=this.ranges();if(t>this.max())return i.length;if(ti[r]._max)e=r+1;else{if(!(tt._max&&(t._max=e[n]._max),this._ranges.splice(i,n-i+1,t)}}else this._ranges.push(t)},I.prototype.isContiguous=function(){return this._ranges.length>1},I.prototype.ranges=function(){return this._ranges},I.prototype._pushRanges=function(t){for(var i=0;i0&&(t+=","),t+=this._ranges[i].toString();return t};function T(){}function U(t,i){return new Promise((function(e,n){var r,s,a,o;r=new b(t),s=new b(i),a=function(t,i){i?n(i):e(t)},(o=new T).data=r,o.tbi=s,o.tbi.fetch((function(t){if(!t)return a(null,"Couldn't access Tabix");var i=C(t,t.byteLength),e=new Uint8Array(i);if(21578324!=y(e,0))return a(null,"Not a tabix index");var n=y(e,4);o.format=y(e,8),o.colSeq=y(e,12),o.colStart=y(e,16),o.colEnd=y(e,20),o.meta=y(e,24),o.skip=y(e,28),y(e,32),o.indices=[];var r=36;o.chrToIndex={},o.indexToChr=[];for(var s=0;s0&&(p+=65536),p0&&(o.indices[u]=new Uint8Array(i,d,r-d))}o.headerMax=f,a(o)}),{timeout:5e4})}))}function E(t){const i=t.Adapters.get("BaseLZAdapter");t.Adapters.add("TabixUrlSource",class extends i{constructor(t){if(super(t),!t.parser_func||!t.url_data&&!t.reader)throw new Error("Tabix source is missing required configuration options");if(this.parser=t.parser_func,this.url_data=t.url_data,this.url_tbi=t.url_tbi||`${this.url_data}.tbi`,this._overfetch=t.overfetch||0,this._overfetch<0||this._overfetch>1)throw new Error("Overfetch must be specified as a fraction (0-1) of the requested region size");this.url_data?this._reader_promise=U(this.url_data,this.url_tbi).catch((function(){throw new Error("Failed to create a tabix reader from the provided URL")})):this._reader_promise=Promise.resolve(t.reader)}_performRequest(t){return new Promise(((i,e)=>{const n=t.start,r=t.end,s=this._overfetch*(r-n),a=t.start-s,o=t.end+s;this._reader_promise.then((n=>{n.fetch(t.chr,a,o,(function(t,n){n&&e(new Error("Could not read requested region. This may indicate an error with the .tbi index.")),i(t)}))}))}))}_normalizeResponse(t){return t.map(this.parser)}})}T.prototype.blocksForRange=function(t,i,e){var n=this.indices[t];if(!n)return[];for(var r=function(t,i){var e,n=[];for(--i,n.push(0),e=1+(t>>26);e<=1+(i>>26);++e)n.push(e);for(e=9+(t>>23);e<=9+(i>>23);++e)n.push(e);for(e=73+(t>>20);e<=73+(i>>20);++e)n.push(e);for(e=585+(t>>17);e<=585+(i>>17);++e)n.push(e);for(e=4681+(t>>14);e<=4681+(i>>14);++e)n.push(e);return n}(i,e),s=[],a=0;a>14,v-1),w=Math.min(e>>14,v-1);for(a=m;a<=w;++a){var g=A(n,f+4+8*a);g&&((!p||g.block=p.block&&C.maxv.offset>=p.offset&&k.push(C)}h=k;var R=[];for(a=0;a0){var L=R[0];for(a=1;a=a.length)return n(l);if(h){var s=new Uint8Array(h);return r.readRecords(s,a[f].minv.offset,l,i,e,o),h=null,++f,t()}var u=a[f],d=u.minv.block,_=u.maxv.block+65536;r.data.slice(d,_-d).fetch((function(i){try{return h=C(i,u.maxv.block-u.minv.block+1),t()}catch(t){return n(null,t)}}))}()}catch(t){n(null,t)}},T.prototype.readRecords=function(t,i,e,n,r,s){t:for(;;){for(var a="";i0&&(f=parseInt(h[this.colEnd-1])),65536&this.format&&++l,l<=r&&f>=n&&e.push(a)}continue t}a+=String.fromCharCode(o)}return}},T.prototype.fetchHeader=function(t,i){var e={metaOnly:!0,skipped:!1,nLines:0};i=i||e,Object.keys(e).forEach((function(t){i.hasOwnProperty(t)||(i[t]=e[t])}));var n=this;n.data.slice(0,n.headerMax).fetch((function(e){if(!e)return t(null,"Fetch failed");for(var r=new Uint8Array(C(e,e.byteLength)),s=0,a="",o=[];s{"use strict";var t={d:(e,o)=>{for(var a in o)t.o(o,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:o[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>i});d3;Math.sqrt(3);function o(t){return JSON.parse(JSON.stringify(t))}const a=["highlight","select","fade","hide"],l=["highlighted","selected","faded","hidden"],s=["unhighlight","deselect","unfade","show"];function n(t){const e=t.Widgets.get("_Button"),n=t.Widgets.get("BaseWidget");const i=function(){const e=t.Layouts.get("tooltip","standard_association");return e.html+='Condition on Variant
    ',e}(),r=function(){const e=t.Layouts.get("toolbar","standard_association");return e.widgets.push({type:"covariates_model",button_html:"Model",button_title:"Show and edit covariates currently in model",position:"left"}),e}();t.Widgets.add("covariates_model",class extends n{initialize(){this.parent_plot.state.model=this.parent_plot.state.model||{},this.parent_plot.state.model.covariates=this.parent_plot.state.model.covariates||[],this.parent_plot.CovariatesModel={button:this,add:t=>{const e=this.parent_plot,a=o(t);"object"==typeof t&&"string"!=typeof a.html&&(a.html="function"==typeof t.toHTML?t.toHTML():t.toString());for(let t=0;t{const e=this.parent_plot;if(void 0===e.state.model.covariates[t])throw new Error(`Unable to remove model covariate, invalid index: ${t.toString()}`);return e.state.model.covariates.splice(t,1),e.applyState(),e.CovariatesModel.updateWidget(),e},removeAll:()=>{const t=this.parent_plot;return t.state.model.covariates=[],t.applyState(),t.CovariatesModel.updateWidget(),t},updateWidget:()=>{this.button.update(),this.button.menu.update()}}}update(){return this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=this.button.menu.inner_selector;if(t.html(""),void 0!==this.parent_plot.state.model.html&&t.append("div").html(this.parent_plot.state.model.html),this.parent_plot.state.model.covariates.length){t.append("h5").html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);const e=t.append("table");this.parent_plot.state.model.covariates.forEach(((t,o)=>{const a="object"==typeof t&&"string"==typeof t.html?t.html:t.toString(),l=e.append("tr");l.append("td").append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","0em").on("click",(()=>this.parent_plot.CovariatesModel.removeByIdx(o))).html("×"),l.append("td").html(a)})),t.append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","4px").html("× Remove All Covariates").on("click",(()=>this.parent_plot.CovariatesModel.removeAll()))}else t.append("i").html("no covariates in model")})),this.button.preUpdate=()=>{let t="Model";const e=this.parent_plot.state.model.covariates.length;if(e){t+=` (${e} ${e>1?"covariates":"covariate"})`}this.button.setHtml(t).disable(!1)},this.button.show()),this}}),t.Widgets.add("data_layers",class extends n{update(){return"string"!=typeof this.layout.button_html&&(this.layout.button_html="Data Layers"),"string"!=typeof this.layout.button_title&&(this.layout.button_title="Manipulate Data Layers (sort, dim, show/hide, etc.)"),this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html("");const t=this.button.menu.inner_selector.append("table");return this.parent_panel._data_layer_ids_by_z_index.slice().reverse().forEach(((e,o)=>{const n=this.parent_panel.data_layers[e],i="string"!=typeof n.layout.name?n.id:n.layout.name,r=t.append("tr");r.append("td").html(i),this.layout.statuses.forEach((t=>{const e=l.indexOf(t),o=a[e];let i,d,u;n._global_statuses[t]?(i=s[e],d=`un${o}AllElements`,u="-highlighted"):(i=a[e],d=`${o}AllElements`,u=""),r.append("td").append("a").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}${u}`).style("margin-left","0em").on("click",(()=>{n[d](),this.button.menu.populate()})).html(i)}));const d=0===o,u=o===this.parent_panel._data_layer_ids_by_z_index.length-1,p=r.append("td");p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${u?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveBack(),this.button.menu.populate()})).html("▾").attr("title","Move layer down (further back)"),p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${d?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveForward(),this.button.menu.populate()})).html("▴").attr("title","Move layer up (further front)"),p.append("a").attr("class","lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red").style("margin-left","0em").on("click",(()=>(confirm(`Are you sure you want to remove the ${i} layer? This cannot be undone.`)&&n.parent.removeDataLayer(e),this.button.menu.populate()))).html("×").attr("title","Remove layer")})),this})),this.button.show()),this}}),t.Layouts.add("tooltip","covariates_model_association",i),t.Layouts.add("toolbar","covariates_model_plot",r)}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const i=n;LzWidgetAddons=e.default})(); //# sourceMappingURL=lz-widget-addons.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-widget-addons.min.js.map b/dist/ext/lz-widget-addons.min.js.map index 0e007b39..7f195841 100644 --- a/dist/ext/lz-widget-addons.min.js.map +++ b/dist/ext/lz-widget-addons.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/ext/lz-widget-addons.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","Math","sqrt","deepCopy","item","JSON","parse","stringify","STATUS_VERBS","STATUS_ADJECTIVES","STATUS_ANTIVERBS","install","LocusZoom","_Button","Widgets","_BaseWidget","covariates_model_tooltip","covariates_model_association","Layouts","html","covariates_model_plot","covariates_model_plot_toolbar","widgets","push","type","button_html","button_title","position","add","this","parent_plot","state","model","covariates","CovariatesModel","button","element_reference","plot","element","toHTML","toString","i","length","applyState","updateWidget","removeByIdx","idx","Error","splice","removeAll","update","menu","setColor","layout","color","setHtml","setTitle","setOnclick","populate","setPopulate","selector","inner_selector","append","table","forEach","covariate","row","attr","style","on","preUpdate","count","disable","show","parent_panel","_data_layer_ids_by_z_index","slice","reverse","id","data_layer","data_layers","name","statuses","status_adj","status_idx","indexOf","status_verb","onclick","highlight","_global_statuses","at_top","at_bottom","td","moveBack","moveForward","confirm","parent","removeDataLayer","use"],"mappings":"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","file":"ext/lz-widget-addons.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {}\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * Optional LocusZoom extension: must be included separately, and after LocusZoom has been loaded\n *\n * This contains (reusable) code to power some (rarely used) demo features:\n * - The \"covariates model\" demo, in which an LZ toolbar widget is populated\n * with information by selecting points on the plot (see \"covariates model\" demo)\n * - The \"data layers\" button, which allows fine control over multiple data layers shown in the same panel\n * (show/hide, fade, change order, etc). This is powerful, but rarely used because showing many datasets in a small\n * space makes data hard to see. (see \"multiple phenotypes layered\" demo)\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n *\n * ```javascript\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```javascript\n * import LocusZoom from 'locuszoom';\n * import WidgetAddons from 'locuszoom/esm/ext/lz-widget-addons';\n * LocusZoom.use(WidgetAddons);\n * ```\n *\n * Then use the features made available by this extension. (see demos and documentation for guidance)\n *\n * @module\n */\nimport {deepCopy} from '../helpers/layouts';\n\n// In order to work in a UMD context, this module imports the top-level LocusZoom symbol\n\nconst STATUS_VERBS = ['highlight', 'select', 'fade', 'hide'];\nconst STATUS_ADJECTIVES = ['highlighted', 'selected', 'faded', 'hidden'];\nconst STATUS_ANTIVERBS = ['unhighlight', 'deselect', 'unfade', 'show'];\n\n\n// LocusZoom plugins work by exporting a function that receives the `LocusZoom` object\n// This allows them to work in many contexts (including script tags and ES6 imports)\nfunction install(LocusZoom) {\n const _Button = LocusZoom.Widgets.get('_Button');\n const _BaseWidget = LocusZoom.Widgets.get('BaseWidget');\n\n\n /**\n * Special button/menu to allow model building by tracking individual covariants. Will track a list of covariate\n * objects and store them in the special `model.covariates` field of plot `state`.\n *\n * This is a prototype widget for building a conditional analysis model, but it performs no calculation\n * functionality beyond building a list of items.\n * @alias module:ext/lz-widget-addons~covariates_model\n * @see module:LocusZoom_Widgets~BaseWidget\n * @param {object} layout\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n */\n class CovariatesModel extends _BaseWidget {\n initialize() {\n // Initialize state.model.covariates\n this.parent_plot.state.model = this.parent_plot.state.model || {};\n this.parent_plot.state.model.covariates = this.parent_plot.state.model.covariates || [];\n // Create an object at the plot level for easy access to interface methods in custom client-side JS\n /**\n * When a covariates model toolbar element is present, create (one) object at the plot level that exposes\n * widget data and state for custom interactions with other plot elements.\n */\n this.parent_plot.CovariatesModel = {\n /** @member {Button} */\n button: this,\n /**\n * Add an element to the model and show a representation of it in the toolbar widget menu. If the\n * element is already part of the model, do nothing (to avoid adding duplicates).\n * When plot state is changed, this will automatically trigger requests for new data accordingly.\n * @param {string|object} element_reference Can be any value that can be put through JSON.stringify()\n * to create a serialized representation of itself.\n */\n add: (element_reference) => {\n const plot = this.parent_plot;\n const element = deepCopy(element_reference);\n if (typeof element_reference == 'object' && typeof element.html != 'string') {\n element.html = ( (typeof element_reference.toHTML == 'function') ? element_reference.toHTML() : element_reference.toString());\n }\n // Check if the element is already in the model covariates array and return if it is.\n for (let i = 0; i < plot.state.model.covariates.length; i++) {\n if (JSON.stringify(plot.state.model.covariates[i]) === JSON.stringify(element)) {\n return plot;\n }\n }\n plot.state.model.covariates.push(element);\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Remove an element from `state.model.covariates` (and from the toolbar widget menu's\n * representation of the state model). When plot state is changed, this will automatically trigger\n * requests for new data accordingly.\n * @param {number} idx Array index of the element, in the `state.model.covariates array`.\n */\n removeByIdx: (idx) => {\n const plot = this.parent_plot;\n if (typeof plot.state.model.covariates[idx] == 'undefined') {\n throw new Error(`Unable to remove model covariate, invalid index: ${idx.toString()}`);\n }\n plot.state.model.covariates.splice(idx, 1);\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Empty the `state.model.covariates` array (and toolbar widget menu representation thereof) of all\n * elements. When plot state is changed, this will automatically trigger requests for new data accordingly\n */\n removeAll: () => {\n const plot = this.parent_plot;\n plot.state.model.covariates = [];\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Manually trigger the update methods on the toolbar widget's button and menu elements to force\n * display of most up-to-date content. Can be used to force the toolbar to reflect changes made, eg if\n * modifying `state.model.covariates` directly instead of via `plot.CovariatesModel`\n */\n updateWidget: () => {\n this.button.update();\n this.button.menu.update();\n },\n };\n }\n\n update() {\n\n if (this.button) {\n return this;\n }\n\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n\n this.button.menu.setPopulate(() => {\n const selector = this.button.menu.inner_selector;\n selector.html('');\n // General model HTML representation\n if (typeof this.parent_plot.state.model.html != 'undefined') {\n selector.append('div').html(this.parent_plot.state.model.html);\n }\n // Model covariates table\n if (!this.parent_plot.state.model.covariates.length) {\n selector.append('i').html('no covariates in model');\n } else {\n selector.append('h5').html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);\n const table = selector.append('table');\n this.parent_plot.state.model.covariates.forEach((covariate, idx) => {\n const html = ((typeof covariate == 'object' && typeof covariate.html == 'string') ? covariate.html : covariate.toString());\n const row = table.append('tr');\n row.append('td').append('button')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}`)\n .style('margin-left', '0em')\n .on('click', () => this.parent_plot.CovariatesModel.removeByIdx(idx))\n .html('×');\n row.append('td')\n .html(html);\n });\n selector.append('button')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}`)\n .style('margin-left', '4px')\n .html('× Remove All Covariates')\n .on('click', () => this.parent_plot.CovariatesModel.removeAll());\n }\n });\n\n this.button.preUpdate = () => {\n let html = 'Model';\n const count = this.parent_plot.state.model.covariates.length;\n if (count) {\n const noun = count > 1 ? 'covariates' : 'covariate';\n html += ` (${count} ${noun})`;\n }\n this.button.setHtml(html).disable(false);\n };\n\n this.button.show();\n\n return this;\n }\n }\n\n\n /**\n * Menu for manipulating multiple data layers in a single panel: show/hide, change order, etc.\n * @alias module:ext/lz-widget-addons~data_layers\n * @see module:LocusZoom_Widgets~BaseWidget\n */\n class DataLayersWidget extends _BaseWidget {\n update() {\n\n if (typeof this.layout.button_html != 'string') {\n this.layout.button_html = 'Data Layers';\n }\n if (typeof this.layout.button_title != 'string') {\n this.layout.button_title = 'Manipulate Data Layers (sort, dim, show/hide, etc.)';\n }\n\n if (this.button) {\n return this;\n }\n\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n this.parent_panel._data_layer_ids_by_z_index.slice().reverse().forEach((id, idx) => {\n const data_layer = this.parent_panel.data_layers[id];\n const name = (typeof data_layer.layout.name != 'string') ? data_layer.id : data_layer.layout.name;\n const row = table.append('tr');\n // Layer name\n row.append('td').html(name);\n // Status toggle buttons\n this.layout.statuses.forEach((status_adj) => {\n const status_idx = STATUS_ADJECTIVES.indexOf(status_adj);\n const status_verb = STATUS_VERBS[status_idx];\n let html, onclick, highlight;\n if (data_layer._global_statuses[status_adj]) {\n html = STATUS_ANTIVERBS[status_idx];\n onclick = `un${status_verb}AllElements`;\n highlight = '-highlighted';\n } else {\n html = STATUS_VERBS[status_idx];\n onclick = `${status_verb}AllElements`;\n highlight = '';\n }\n row.append('td').append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}${highlight}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer[onclick]();\n this.button.menu.populate();\n })\n .html(html);\n });\n // Sort layer buttons\n const at_top = (idx === 0);\n const at_bottom = (idx === (this.parent_panel._data_layer_ids_by_z_index.length - 1));\n const td = row.append('td');\n td.append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${at_bottom ? '-disabled' : ''}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer.moveBack(); this.button.menu.populate();\n })\n .html('▾')\n .attr('title', 'Move layer down (further back)');\n td.append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${at_top ? '-disabled' : ''}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer.moveForward(); this.button.menu.populate();\n })\n .html('▴')\n .attr('title', 'Move layer up (further front)');\n td.append('a')\n .attr('class', 'lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red')\n .style('margin-left', '0em')\n .on('click', () => {\n if (confirm(`Are you sure you want to remove the ${name} layer? This cannot be undone.`)) {\n data_layer.parent.removeDataLayer(id);\n }\n return this.button.menu.populate();\n })\n .html('×')\n .attr('title', 'Remove layer');\n });\n return this;\n });\n\n this.button.show();\n\n return this;\n }\n }\n\n const covariates_model_tooltip = function () {\n const covariates_model_association = LocusZoom.Layouts.get('tooltip', 'standard_association');\n covariates_model_association.html += 'Condition on Variant
    ';\n return covariates_model_association;\n }();\n\n const covariates_model_plot = function () {\n const covariates_model_plot_toolbar = LocusZoom.Layouts.get('toolbar', 'standard_association');\n covariates_model_plot_toolbar.widgets.push({\n type: 'covariates_model',\n button_html: 'Model',\n button_title: 'Show and edit covariates currently in model',\n position: 'left',\n });\n return covariates_model_plot_toolbar;\n }();\n\n LocusZoom.Widgets.add('covariates_model', CovariatesModel);\n LocusZoom.Widgets.add('data_layers', DataLayersWidget);\n\n LocusZoom.Layouts.add('tooltip', 'covariates_model_association', covariates_model_tooltip);\n LocusZoom.Layouts.add('toolbar', 'covariates_model_plot', covariates_model_plot);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/external \"d3\"","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/ext/lz-widget-addons.js"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","d3","Math","sqrt","deepCopy","item","JSON","parse","stringify","STATUS_VERBS","STATUS_ADJECTIVES","STATUS_ANTIVERBS","install","LocusZoom","_Button","Widgets","_BaseWidget","covariates_model_tooltip","covariates_model_association","Layouts","html","covariates_model_plot","covariates_model_plot_toolbar","widgets","push","type","button_html","button_title","position","add","this","parent_plot","state","model","covariates","CovariatesModel","button","element_reference","plot","element","toHTML","toString","i","length","applyState","updateWidget","removeByIdx","idx","Error","splice","removeAll","update","menu","setColor","layout","color","setHtml","setTitle","setOnclick","populate","setPopulate","selector","inner_selector","append","table","forEach","covariate","row","attr","style","on","preUpdate","count","disable","show","parent_panel","_data_layer_ids_by_z_index","slice","reverse","id","data_layer","data_layers","name","statuses","status_adj","status_idx","indexOf","status_verb","onclick","highlight","_global_statuses","at_top","at_bottom","td","moveBack","moveForward","confirm","parent","removeDataLayer","use"],"mappings":"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","file":"ext/lz-widget-addons.min.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {},\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable,\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * Optional LocusZoom extension: must be included separately, and after LocusZoom has been loaded\n *\n * This contains (reusable) code to power some (rarely used) demo features:\n * - The \"covariates model\" demo, in which an LZ toolbar widget is populated\n * with information by selecting points on the plot (see \"covariates model\" demo)\n * - The \"data layers\" button, which allows fine control over multiple data layers shown in the same panel\n * (show/hide, fade, change order, etc). This is powerful, but rarely used because showing many datasets in a small\n * space makes data hard to see. (see \"multiple phenotypes layered\" demo)\n *\n * ### Loading and usage\n * The page must incorporate and load all libraries before this file can be used, including:\n * - LocusZoom\n *\n * To use in an environment without special JS build tooling, simply load the extension file as JS from a CDN (after any dependencies):\n *\n * ```javascript\n * \n * ```\n *\n * To use with ES6 modules, the plugin must be loaded and registered explicitly before use:\n * ```javascript\n * import LocusZoom from 'locuszoom';\n * import WidgetAddons from 'locuszoom/esm/ext/lz-widget-addons';\n * LocusZoom.use(WidgetAddons);\n * ```\n *\n * Then use the features made available by this extension. (see demos and documentation for guidance)\n *\n * @module\n */\nimport {deepCopy} from '../helpers/layouts';\n\n// In order to work in a UMD context, this module imports the top-level LocusZoom symbol\n\nconst STATUS_VERBS = ['highlight', 'select', 'fade', 'hide'];\nconst STATUS_ADJECTIVES = ['highlighted', 'selected', 'faded', 'hidden'];\nconst STATUS_ANTIVERBS = ['unhighlight', 'deselect', 'unfade', 'show'];\n\n\n// LocusZoom plugins work by exporting a function that receives the `LocusZoom` object\n// This allows them to work in many contexts (including script tags and ES6 imports)\nfunction install(LocusZoom) {\n const _Button = LocusZoom.Widgets.get('_Button');\n const _BaseWidget = LocusZoom.Widgets.get('BaseWidget');\n\n\n /**\n * Special button/menu to allow model building by tracking individual covariants. Will track a list of covariate\n * objects and store them in the special `model.covariates` field of plot `state`.\n *\n * This is a prototype widget for building a conditional analysis model, but it performs no calculation\n * functionality beyond building a list of items.\n * @alias module:ext/lz-widget-addons~covariates_model\n * @see module:LocusZoom_Widgets~BaseWidget\n * @param {object} layout\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n */\n class CovariatesModel extends _BaseWidget {\n initialize() {\n // Initialize state.model.covariates\n this.parent_plot.state.model = this.parent_plot.state.model || {};\n this.parent_plot.state.model.covariates = this.parent_plot.state.model.covariates || [];\n // Create an object at the plot level for easy access to interface methods in custom client-side JS\n /**\n * When a covariates model toolbar element is present, create (one) object at the plot level that exposes\n * widget data and state for custom interactions with other plot elements.\n */\n this.parent_plot.CovariatesModel = {\n /** @member {Button} */\n button: this,\n /**\n * Add an element to the model and show a representation of it in the toolbar widget menu. If the\n * element is already part of the model, do nothing (to avoid adding duplicates).\n * When plot state is changed, this will automatically trigger requests for new data accordingly.\n * @param {string|object} element_reference Can be any value that can be put through JSON.stringify()\n * to create a serialized representation of itself.\n */\n add: (element_reference) => {\n const plot = this.parent_plot;\n const element = deepCopy(element_reference);\n if (typeof element_reference == 'object' && typeof element.html != 'string') {\n element.html = ( (typeof element_reference.toHTML == 'function') ? element_reference.toHTML() : element_reference.toString());\n }\n // Check if the element is already in the model covariates array and return if it is.\n for (let i = 0; i < plot.state.model.covariates.length; i++) {\n if (JSON.stringify(plot.state.model.covariates[i]) === JSON.stringify(element)) {\n return plot;\n }\n }\n plot.state.model.covariates.push(element);\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Remove an element from `state.model.covariates` (and from the toolbar widget menu's\n * representation of the state model). When plot state is changed, this will automatically trigger\n * requests for new data accordingly.\n * @param {number} idx Array index of the element, in the `state.model.covariates array`.\n */\n removeByIdx: (idx) => {\n const plot = this.parent_plot;\n if (typeof plot.state.model.covariates[idx] == 'undefined') {\n throw new Error(`Unable to remove model covariate, invalid index: ${idx.toString()}`);\n }\n plot.state.model.covariates.splice(idx, 1);\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Empty the `state.model.covariates` array (and toolbar widget menu representation thereof) of all\n * elements. When plot state is changed, this will automatically trigger requests for new data accordingly\n */\n removeAll: () => {\n const plot = this.parent_plot;\n plot.state.model.covariates = [];\n plot.applyState();\n plot.CovariatesModel.updateWidget();\n return plot;\n },\n /**\n * Manually trigger the update methods on the toolbar widget's button and menu elements to force\n * display of most up-to-date content. Can be used to force the toolbar to reflect changes made, eg if\n * modifying `state.model.covariates` directly instead of via `plot.CovariatesModel`\n */\n updateWidget: () => {\n this.button.update();\n this.button.menu.update();\n },\n };\n }\n\n update() {\n\n if (this.button) {\n return this;\n }\n\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n\n this.button.menu.setPopulate(() => {\n const selector = this.button.menu.inner_selector;\n selector.html('');\n // General model HTML representation\n if (typeof this.parent_plot.state.model.html != 'undefined') {\n selector.append('div').html(this.parent_plot.state.model.html);\n }\n // Model covariates table\n if (!this.parent_plot.state.model.covariates.length) {\n selector.append('i').html('no covariates in model');\n } else {\n selector.append('h5').html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);\n const table = selector.append('table');\n this.parent_plot.state.model.covariates.forEach((covariate, idx) => {\n const html = ((typeof covariate == 'object' && typeof covariate.html == 'string') ? covariate.html : covariate.toString());\n const row = table.append('tr');\n row.append('td').append('button')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}`)\n .style('margin-left', '0em')\n .on('click', () => this.parent_plot.CovariatesModel.removeByIdx(idx))\n .html('×');\n row.append('td')\n .html(html);\n });\n selector.append('button')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}`)\n .style('margin-left', '4px')\n .html('× Remove All Covariates')\n .on('click', () => this.parent_plot.CovariatesModel.removeAll());\n }\n });\n\n this.button.preUpdate = () => {\n let html = 'Model';\n const count = this.parent_plot.state.model.covariates.length;\n if (count) {\n const noun = count > 1 ? 'covariates' : 'covariate';\n html += ` (${count} ${noun})`;\n }\n this.button.setHtml(html).disable(false);\n };\n\n this.button.show();\n\n return this;\n }\n }\n\n\n /**\n * Menu for manipulating multiple data layers in a single panel: show/hide, change order, etc.\n * @alias module:ext/lz-widget-addons~data_layers\n * @see module:LocusZoom_Widgets~BaseWidget\n */\n class DataLayersWidget extends _BaseWidget {\n update() {\n\n if (typeof this.layout.button_html != 'string') {\n this.layout.button_html = 'Data Layers';\n }\n if (typeof this.layout.button_title != 'string') {\n this.layout.button_title = 'Manipulate Data Layers (sort, dim, show/hide, etc.)';\n }\n\n if (this.button) {\n return this;\n }\n\n this.button = new _Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n this.parent_panel._data_layer_ids_by_z_index.slice().reverse().forEach((id, idx) => {\n const data_layer = this.parent_panel.data_layers[id];\n const name = (typeof data_layer.layout.name != 'string') ? data_layer.id : data_layer.layout.name;\n const row = table.append('tr');\n // Layer name\n row.append('td').html(name);\n // Status toggle buttons\n this.layout.statuses.forEach((status_adj) => {\n const status_idx = STATUS_ADJECTIVES.indexOf(status_adj);\n const status_verb = STATUS_VERBS[status_idx];\n let html, onclick, highlight;\n if (data_layer._global_statuses[status_adj]) {\n html = STATUS_ANTIVERBS[status_idx];\n onclick = `un${status_verb}AllElements`;\n highlight = '-highlighted';\n } else {\n html = STATUS_VERBS[status_idx];\n onclick = `${status_verb}AllElements`;\n highlight = '';\n }\n row.append('td').append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-${this.layout.color}${highlight}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer[onclick]();\n this.button.menu.populate();\n })\n .html(html);\n });\n // Sort layer buttons\n const at_top = (idx === 0);\n const at_bottom = (idx === (this.parent_panel._data_layer_ids_by_z_index.length - 1));\n const td = row.append('td');\n td.append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${at_bottom ? '-disabled' : ''}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer.moveBack(); this.button.menu.populate();\n })\n .html('▾')\n .attr('title', 'Move layer down (further back)');\n td.append('a')\n .attr('class', `lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${at_top ? '-disabled' : ''}`)\n .style('margin-left', '0em')\n .on('click', () => {\n data_layer.moveForward(); this.button.menu.populate();\n })\n .html('▴')\n .attr('title', 'Move layer up (further front)');\n td.append('a')\n .attr('class', 'lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red')\n .style('margin-left', '0em')\n .on('click', () => {\n if (confirm(`Are you sure you want to remove the ${name} layer? This cannot be undone.`)) {\n data_layer.parent.removeDataLayer(id);\n }\n return this.button.menu.populate();\n })\n .html('×')\n .attr('title', 'Remove layer');\n });\n return this;\n });\n\n this.button.show();\n\n return this;\n }\n }\n\n const covariates_model_tooltip = function () {\n const covariates_model_association = LocusZoom.Layouts.get('tooltip', 'standard_association');\n covariates_model_association.html += 'Condition on Variant
    ';\n return covariates_model_association;\n }();\n\n const covariates_model_plot = function () {\n const covariates_model_plot_toolbar = LocusZoom.Layouts.get('toolbar', 'standard_association');\n covariates_model_plot_toolbar.widgets.push({\n type: 'covariates_model',\n button_html: 'Model',\n button_title: 'Show and edit covariates currently in model',\n position: 'left',\n });\n return covariates_model_plot_toolbar;\n }();\n\n LocusZoom.Widgets.add('covariates_model', CovariatesModel);\n LocusZoom.Widgets.add('data_layers', DataLayersWidget);\n\n LocusZoom.Layouts.add('tooltip', 'covariates_model_association', covariates_model_tooltip);\n LocusZoom.Layouts.add('toolbar', 'covariates_model_plot', covariates_model_plot);\n}\n\nif (typeof LocusZoom !== 'undefined') {\n // Auto-register the plugin when included as a script tag. ES6 module users must register via LocusZoom.use()\n // eslint-disable-next-line no-undef\n LocusZoom.use(install);\n}\n\n\nexport default install;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/locuszoom.app.min.js b/dist/locuszoom.app.min.js index 9477f929..e7e72466 100644 --- a/dist/locuszoom.app.min.js +++ b/dist/locuszoom.app.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.2 */ -var LocusZoom;(()=>{var t={483:(t,e,s)=>{"use strict";const i=s(919);t.exports=function(t,...e){if(!t){if(1===e.length&&e[0]instanceof Error)throw e[0];throw new i(e)}}},919:(t,e,s)=>{"use strict";const i=s(820);t.exports=class extends Error{constructor(t){super(t.filter((t=>""!==t)).map((t=>"string"==typeof t?t:t instanceof Error?t.message:i(t))).join(" ")||"Unknown error"),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e.assert)}}},820:t=>{"use strict";t.exports=function(...t){try{return JSON.stringify.apply(null,t)}catch(t){return"[Cannot display object: "+t.message+"]"}}},681:(t,e,s)=>{"use strict";const i=s(483),a={};e.B=class{constructor(){this._items=[],this.nodes=[]}add(t,e){const s=[].concat((e=e||{}).before||[]),a=[].concat(e.after||[]),n=e.group||"?",o=e.sort||0;i(!s.includes(n),`Item cannot come before itself: ${n}`),i(!s.includes("?"),"Item cannot come before unassociated items"),i(!a.includes(n),`Item cannot come after itself: ${n}`),i(!a.includes("?"),"Item cannot come after unassociated items"),Array.isArray(t)||(t=[t]);for(const e of t){const t={seq:this._items.length,sort:o,before:s,after:a,group:n,node:e};this._items.push(t)}if(!e.manual){const t=this._sort();i(t,"item","?"!==n?`added into group ${n}`:"","created a dependencies error")}return this.nodes}merge(t){Array.isArray(t)||(t=[t]);for(const e of t)if(e)for(const t of e._items)this._items.push(Object.assign({},t));this._items.sort(a.mergeSort);for(let t=0;tt.sort===e.sort?0:t.sort{function e(t){if("string"==typeof t.source.flags)return t.source.flags;var e=[];return t.global&&e.push("g"),t.ignoreCase&&e.push("i"),t.multiline&&e.push("m"),t.sticky&&e.push("y"),t.unicode&&e.push("u"),e.join("")}t.exports=function t(s){if("function"==typeof s)return s;var i=Array.isArray(s)?[]:{};for(var a in s){var n=s[a],o={}.toString.call(n).slice(8,-1);i[a]="Array"==o||"Object"==o?t(n):"Date"==o?new Date(n.getTime()):"RegExp"==o?RegExp(n.source,e(n)):n}return i}}},e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={exports:{}};return t[i](a,a.exports,s),a.exports}s.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return s.d(e,{a:e}),e},s.d=(t,e)=>{for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),s.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";s.d(i,{default:()=>ds});var t={};s.r(t),s.d(t,{AssociationLZ:()=>M,BaseAdapter:()=>$,BaseApiAdapter:()=>z,BaseLZAdapter:()=>k,BaseUMAdapter:()=>E,GeneConstraintLZ:()=>A,GeneLZ:()=>N,GwasCatalogLZ:()=>S,LDServer:()=>O,PheWASLZ:()=>j,RecombLZ:()=>T,StaticSource:()=>L});var e={};s.r(e),s.d(e,{htmlescape:()=>F,is_numeric:()=>H,log10:()=>D,logtoscinotation:()=>U,neglog10:()=>B,scinotation:()=>q,urlencode:()=>G});var a={};s.r(a),s.d(a,{BaseWidget:()=>yt,_Button:()=>ft,display_options:()=>Ot,download:()=>vt,download_png:()=>wt,filter_field:()=>xt,menu:()=>St,move_panel_down:()=>kt,move_panel_up:()=>zt,region_scale:()=>bt,remove_panel:()=>$t,resize_to_data:()=>Nt,set_state:()=>Tt,shift_region:()=>Et,title:()=>mt,toggle_legend:()=>At,zoom_region:()=>Mt});var n={};s.r(n),s.d(n,{categorical_bin:()=>Vt,effect_direction:()=>Qt,if_value:()=>Zt,interpolate:()=>Xt,numerical_bin:()=>Kt,ordinal_cycle:()=>Wt,stable_choice:()=>Yt});var o={};s.r(o),s.d(o,{BaseDataLayer:()=>ie,annotation_track:()=>ne,arcs:()=>he,category_scatter:()=>me,genes:()=>de,highlight_regions:()=>re,line:()=>_e,orthogonal_line:()=>ge,scatter:()=>fe});var r={};s.r(r),s.d(r,{data_layer:()=>es,panel:()=>ss,plot:()=>is,toolbar:()=>ts,toolbar_widgets:()=>Qe,tooltip:()=>Xe});const l="0.14.0-beta.2";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}class d{constructor(t,e,s={},i=null,a=null){this.key=t,this.value=e,this.metadata=s,this.prev=i,this.next=a}}class u{constructor(t=3){if(this._max_size=t,this._cur_size=0,this._store=new Map,this._head=null,this._tail=null,null===t||t<0)throw new Error('Cache "max_size" must be >= 0')}has(t){return this._store.has(t)}get(t){const e=this._store.get(t);return e?(this._head!==e&&this.add(t,e.value),e.value):null}add(t,e,s={}){if(0===this._max_size)return;const i=this._store.get(t);i&&this._remove(i);const a=new d(t,e,s,null,this._head);if(this._head?this._head.prev=a:this._tail=a,this._head=a,this._store.set(t,a),this._max_size>=0&&this._cur_size>=this._max_size){const t=this._tail;this._tail=this._tail.prev,this._remove(t)}this._cur_size+=1}clear(){this._head=null,this._tail=null,this._cur_size=0,this._store=new Map}remove(t){const e=this._store.get(t);return!!e&&(this._remove(e),!0)}_remove(t){null!==t.prev?t.prev.next=t.next:this._head=t.next,null!==t.next?t.next.prev=t.prev:this._tail=t.prev,this._store.delete(t.key),this._cur_size-=1}find(t){let e=this._head;for(;e;){const s=e.next;if(t(e))return e;e=s}}}var _=s(957),p=s.n(_);function g(t){return"object"!=typeof t?t:p()(t)}var y=s(681);function f(t,e,s,i=!0){if(!s.length)return[];const a=s.map((t=>function(t){const e=/^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(t);if(!e)throw new Error(`Unable to parse dependency specification: ${t}`);let{name_alone:s,name_deps:i,deps:a}=e.groups;return s?[s,[]]:(a=a.split(/\s*,\s*/),[i,a])}(t))),n=new Map(a),o=new y.B;for(let[t,e]of n.entries())try{o.add(t,{after:e,group:t})}catch(e){throw new Error(`Invalid or possible circular dependency specification for: ${t}`)}const r=o.nodes,l=new Map;for(let s of r){const i=e.get(s);if(!i)throw new Error(`Data has been requested from source '${s}', but no matching source was provided`);const a=n.get(s)||[],o=Promise.all(a.map((t=>l.get(t)))).then((e=>{const a=Object.assign({_provider_name:s},t);return i.getData(a,...e)}));l.set(s,o)}return Promise.all([...l.values()]).then((t=>i?t[t.length-1]:t))}function m(t,e){const s=new Map;for(let i of t){const t=i[e];if(void 0===t)throw new Error(`All records must specify a value for the field "${e}"`);if("object"==typeof t)throw new Error("Attempted to group on a field with non-primitive values");let a=s.get(t);a||(a=[],s.set(t,a)),a.push(i)}return s}function b(t,e,s,i,a){const n=m(s,a),o=[];for(let s of e){const e=s[i],a=n.get(e)||[];a.length?o.push(...a.map((t=>Object.assign({},g(t),g(s))))):"inner"!==t&&o.push(g(s))}if("outer"===t){const t=m(e,i);for(let e of s){const s=e[a];(t.get(s)||[]).length||o.push(g(e))}}return o}function x(t,e,s,i){return b("left",...arguments)}const v=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function w(t,e=!1){const s=t&&t.match(v);if(s)return s.slice(1);if(e)return null;throw new Error(`Could not understand marker format for ${t}. Should be of format chr:pos or chr:pos_ref/alt`)}class ${constructor(){throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.')}}class z extends ${}class k extends class extends class{constructor(t={}){this._config=t;const{cache_enabled:e=!0,cache_size:s=3}=t;this._enable_cache=e,this._cache=new u(s)}_buildRequestOptions(t,e){return Object.assign({},t)}_getCacheKey(t){if(this._enable_cache)throw new Error("Method not implemented");return null}_performRequest(t){throw new Error("Not implemented")}_normalizeResponse(t,e){return t}_annotateRecords(t,e){return t}_postProcessResponse(t,e){return t}getData(t={},...e){t=this._buildRequestOptions(t,...e);const s=this._getCacheKey(t);let i;return this._enable_cache&&this._cache.has(s)?i=this._cache.get(s):(i=Promise.resolve(this._performRequest(t)).then((e=>this._normalizeResponse(e,t))),this._cache.add(s,i,t._cache_meta),i.catch((t=>this._cache.remove(s)))),i.then((t=>g(t))).then((e=>this._annotateRecords(e,t))).then((e=>this._postProcessResponse(e,t)))}}{constructor(t={}){super(t),this._url=t.url}_getCacheKey(t){return this._getURL(t)}_getURL(t){return this._url}_performRequest(t){const e=this._getURL(t);if(!this._url)throw new Error('Web based resources must specify a resource URL as option "url"');return fetch(e).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}_normalizeResponse(t,e){return"string"==typeof t?JSON.parse(t):t}}{constructor(t={}){t.params&&(console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'),Object.assign(t,t.params||{}),delete t.params),super(t);const{prefix_namespace:e=!0,limit_fields:s}=t;this._prefix_namespace=e,this._limit_fields=!!s&&new Set(s)}_getCacheKey(t){let{chr:e,start:s,end:i}=t;const a=this._cache.find((({metadata:t})=>e===t.chr&&s>=t.start&&i<=t.end));return a&&({chr:e,start:s,end:i}=a.metadata),t._cache_meta={chr:e,start:s,end:i},`${e}_${s}_${i}`}_postProcessResponse(t,e){if(!this._prefix_namespace||!Array.isArray(t))return t;const{_limit_fields:s}=this,{_provider_name:i}=e;return t.map((t=>Object.entries(t).reduce(((t,[e,a])=>(s&&!s.has(e)||(t[`${i}:${e}`]=a),t)),{})))}_findPrefixedKey(t,e){const s=new RegExp(`:${e}$`),i=Object.keys(t).find((t=>s.test(t)));if(!i)throw new Error(`Could not locate the required key name: ${e} in dependent data`);return i}}class E extends k{constructor(t={}){super(t),this._genome_build=t.genome_build||t.build}_validateBuildSource(t,e){if(t&&e||!t&&!e)throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`)}_normalizeResponse(t,e){let s=super._normalizeResponse(...arguments);if(s=s.data||s,Array.isArray(s))return s;const i=Object.keys(s),a=s[i[0]].length;if(!i.every((function(t){return s[t].length===a})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const n=[],o=Object.keys(s);for(let t=0;t25||"GRCh38"===s)return Promise.resolve([]);e=`{${e.join(" ")} }`;const i=this._getURL(t),a=JSON.stringify({query:e});return fetch(i,{method:"POST",body:a,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}_normalizeResponse(t){if("string"!=typeof t)return t;return JSON.parse(t).data}}class O extends E{constructor(t){t.limit_fields||(t.limit_fields=["variant2","position2","correlation"]),super(t)}__find_ld_refvar(t,e){const s=this._findPrefixedKey(e[0],"variant"),i=this._findPrefixedKey(e[0],"log_pvalue");let a,n={};if(t.ldrefvar)a=t.ldrefvar,n=e.find((t=>t[s]===a))||{};else{let t=0;for(let o of e){const{[s]:e,[i]:r}=o;r>t&&(t=r,a=e,n=o)}}n.lz_is_ld_refvar=!0;const o=w(a,!0);if(!o)throw new Error("Could not request LD for a missing or incomplete marker format");const[r,l,h,c]=o;a=`${r}:${l}`,h&&c&&(a+=`_${h}/${c}`);const d=+l;return d&&t.ldrefvar&&t.chr&&(r!==t.chr||dt.end)?(t.ldrefvar=null,this.__find_ld_refvar(t,e)):a}_buildRequestOptions(t,e){if(!e)throw new Error("LD request must depend on association data");const s=super._buildRequestOptions(...arguments);if(!e.length)return s._skip_request=!0,s;s.ld_refvar=this.__find_ld_refvar(t,e);const i=t.genome_build||this._config.build||"GRCh37";let a=t.ld_source||this._config.source||"1000G";const n=t.ld_pop||this._config.population||"ALL";return"1000G"===a&&"GRCh38"===i&&(a="1000G-FRZ09"),this._validateBuildSource(i,null),Object.assign({},s,{genome_build:i,ld_source:a,ld_population:n})}_getURL(t){const e=this._config.method||"rsquare",{chr:s,start:i,end:a,ld_refvar:n,genome_build:o,ld_source:r,ld_population:l}=t;return[super._getURL(t),"genome_builds/",o,"/references/",r,"/populations/",l,"/variants","?correlation=",e,"&variant=",encodeURIComponent(n),"&chrom=",encodeURIComponent(s),"&start=",encodeURIComponent(i),"&stop=",encodeURIComponent(a)].join("")}_getCacheKey(t){const e=super._getCacheKey(t),{ld_refvar:s,ld_source:i,ld_population:a}=t;return`${e}_${s}_${i}_${a}`}_performRequest(t){if(t._skip_request)return Promise.resolve([]);const e=this._getURL(t);let s={data:{}},i=function(t){return fetch(t).then().then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){s.data[e]=(s.data[e]||[]).concat(t.data[e])})),t.next?i(t.next):s}))};return i(e)}}class T extends E{constructor(t){t.limit_fields||(t.limit_fields=["position","recomb_rate"]),super(t)}_getURL(t){const e=t.genome_build||this._config.build;let s=this._config.source;this._validateBuildSource(e,s);const i=e?`&build=${e}`:` and id in ${s}`;return`${super._getURL(t)}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${i}`}}class L extends k{constructor(t={}){super(...arguments);const{data:e}=t;if(!e||Array.isArray(t))throw new Error("'StaticSource' must provide data as required option 'config.data'");this._data=e}_performRequest(t){return Promise.resolve(this._data)}}class j extends E{_getURL(t){const e=(t.genome_build?[t.genome_build]:null)||this._config.build;if(!e||!Array.isArray(e)||!e.length)throw new Error(["Adapter",this.constructor.name,"requires that you specify array of one or more desired genome build names"].join(" "));return[super._getURL(t),"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",e.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}_getCacheKey(t){return this._getURL(t)}}const P=new c;for(let[e,s]of Object.entries(t))P.add(e,s);P.add("StaticJSON",L),P.add("LDLZ2",O);const R=P,I=d3,C={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function D(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function B(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function U(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function q(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function F(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function H(t){return"number"==typeof t}function G(t){return encodeURIComponent(t)}const J=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,s]of Object.entries(e))J.add(t,s);const Z=J;class K{constructor(t){if(!/^(?:\w+:\w+|^\w+)(?:\|\w+)*$/.test(t))throw new Error(`Invalid field specifier: '${t}'`);const[e,...s]=t.split("|");this.full_name=t,this.field_name=e,this.transformations=s.map((t=>Z.get(t)))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[this.field_name]?s=t[this.field_name]:e&&void 0!==e[this.field_name]&&(s=e[this.field_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const V=/^(\*|[\w]+)/,W=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function Y(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=V.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=V.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=W.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function X(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function Q(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const n=t[s.attr];1===e.length?void 0!==n&&a.push([s.attr]):a.push(...Q(n,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...Q(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...Q(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[n,o]of Object.entries(t))a.push(...Q(o,e).map((t=>[n].concat(t)))),"*"===s.attr&&a.push(...Q(o,i).map((t=>[n].concat(t))))}}else if(s.attrs)for(let[e,n]of Object.entries(t)){const[t,o,r]=X(n,s.attrs);r===s.value&&a.push(...Q(n,i).map((t=>[e].concat(t))))}const n=(o=a,r=JSON.stringify,[...new Map(o.map((t=>[r(t),t]))).values()]);var o,r;return n.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),n}function tt(t,e){const s=function(t,e){let s=[];for(let i of Q(t,e))s.push(X(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=Y(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const et=Math.sqrt(3),st={draw(t,e){const s=-Math.sqrt(e/(3*et));t.moveTo(0,2*-s),t.lineTo(-et*s,s),t.lineTo(et*s,s),t.closePath()}};function it(t,e){if(e=e||{},!t||"object"!=typeof t||"object"!=typeof e)throw new Error("Layout and shared namespaces must be provided as objects");for(let[s,i]of Object.entries(t))"namespace"===s?Object.keys(i).forEach((t=>{const s=e[t];s&&(i[t]=s)})):null!==i&&"object"==typeof i&&(t[s]=it(i,e));return t}function at(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=at(t[s],e[s])):t[s]=nt(e[s])}return t}function nt(t){return JSON.parse(JSON.stringify(t))}function ot(t){if(!t)return null;if("triangledown"===t)return st;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return I[e]||null}function rt(t,e,s=null){const i=new Set;if(!s){if(!e.length)return i;const t=e.join("|");s=new RegExp(`(?:{{)?(?:#if *)?((?:${t}):\\w+)`,"g")}for(const a of Object.values(t)){const t=typeof a;let n=[];if("string"===t){let t;for(;null!==(t=s.exec(a));)n.push(t[1])}else{if(null===a||"object"!==t)continue;n=rt(a,e,s)}for(let t of n)i.add(t)}return i}function lt(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>lt(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,n)=>(a[n]=lt(t[n],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const n=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(n,s)}}function ht(t,e,s){return function(t,e,s){return tt(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function ct(t,e){return function(t,e){return tt(t,e).map((t=>t[2]))}(t,e)}class dt{constructor(t,e,s){this._callable=ls.get(t),this._initiator=e,this._params=s||[]}getData(t,...e){const s={plot_state:t,data_layer:this._initiator};return Promise.resolve(this._callable(s,e,...this._params))}}const ut=class{constructor(t){this._sources=t}config_to_sources(t={},e=[],s){const i=new Map,a=Object.keys(t);let n=e.find((t=>"fetch"===t.type));n||(n={type:"fetch",from:a},e.unshift(n));const o=/^\w+$/;for(let[e,s]of Object.entries(t)){if(!o.test(e))throw new Error(`Invalid namespace name: '${e}'. Must contain only alphanumeric characters`);const t=this._sources.get(s);if(!t)throw new Error(`A data layer has requested an item not found in DataSources: data type '${e}' from ${s}`);i.set(e,t),n.from.find((t=>t.split("(")[0]===e))||n.from.push(e)}let r=Array.from(n.from);for(let t of e){let{type:e,name:a,requires:n,params:o}=t;if("fetch"!==e){let l=0;if(a||(a=t.name=`join${l}`,l+=1),i.has(a))throw new Error(`Configuration error: within a layer, join name '${a}' must be unique`);n.forEach((t=>{if(!i.has(t))throw new Error(`Data operation cannot operate on unknown provider '${t}'`)}));const h=new dt(e,s,o);i.set(a,h),r.push(`${a}(${n.join(", ")})`)}}return[i,r]}getData(t,e,s){return s.length?f(t,e,s,!0):Promise.resolve([])}};function _t(){return{showing:!1,selector:null,content_selector:null,hide_delay:null,show:(t,e)=>(this.curtain.showing||(this.curtain.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&>(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function pt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function gt(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class yt{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&>(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class ft{constructor(t){if(!(t instanceof yt))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),n=this.menu.outer_selector.node().getBoundingClientRect(),o=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-n.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-n.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),_=Math.min(o+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${_}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(gt,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class mt extends yt{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class bt extends yt{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(qt(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&>(this.selector,this.layout.style),this}}class xt extends yt{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class vt extends yt{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=I.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+I.select(this).attr("dy").substring(-2).slice(0,-2);I.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class wt extends vt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((n,o)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{n(URL.createObjectURL(t))}))},r.src=t}))}))}}class $t extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),I.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),I.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class zt extends yt{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class kt extends yt{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot._panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class Et extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${qt(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class Mt extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class St extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class Nt extends yt{constructor(t){super(...arguments)}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class At extends yt{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new ft(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class Ot extends yt{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,n={};s.forEach((t=>{const e=a[t];void 0!==e&&(n[t]=nt(e))})),this._selected_item="default",this.button=new ft(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,o=(a,o,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==o[t];i.layout[t]=e?o[t]:n[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return o(r,n,"default"),a.options.forEach(((t,e)=>o(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class Tt extends yt{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new ft(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,n)=>{const o=s.append("tr"),r=`${e}${n}`;o.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",n).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),o.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const Lt=new c;for(let[t,e]of Object.entries(a))Lt.add(t,e);const jt=Lt;class Pt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{this.addWidget(t)})),"panel"===this.type&&I.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}addWidget(t){try{const e=jt.create(t.type,t,this);return this.widgets.push(e),e}catch(t){console.warn("Failed to create widget"),console.error(t)}}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot._panel_boundaries.dragging||this.parent_plot._interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=I.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=I.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const Rt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:14,hidden:!1};class It{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=at(this.parent.layout.legend||{},Rt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((a=>{const n=this.parent.data_layers[a].layout.legend;Array.isArray(n)&&n.forEach((a=>{const n=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),o=+a.label_size||+this.layout.label_size;let r=0,l=o/2+t/2;i=Math.max(i,o+t);const h=a.shape||"",c=ot(h);if("line"===h){const e=+a.length||16,s=o/4+t/2;n.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(gt,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;n.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(gt,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if("ribbon"===h){const e=+a.width||25,s=+a.height||e,i="horizontal"===(a.orientation||"vertical");let o=a.color_stops;const h=n.append("g"),c=h.append("g"),d=h.append("g");let u=0;if(a.tick_labels){let t;t=i?[0,e*o.length-1]:[s*o.length-1,0];const n=I.scaleLinear().domain(I.extent(a.tick_labels)).range(t),r=(i?I.axisTop:I.axisRight)(n).tickSize(3).tickValues(a.tick_labels).tickFormat((t=>t));d.call(r).attr("class","lz-axis"),u=d.node().getBoundingClientRect().height}i?(d.attr("transform",`translate(0, ${u})`),c.attr("transform",`translate(0, ${u})`)):(h.attr("transform","translate(5, 0)"),d.attr("transform",`translate(${e}, 0)`)),i||(o=o.slice(),o.reverse());for(let t=0;tt&&a>this.parent.parent.layout.width&&(s+=i,e=t,n.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(n)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const Ct={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class Dt{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"!=typeof t.id||!t.id)throw new Error('Panel layouts must specify "id"');if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`);this.id=t.id,this._initialized=!1,this._layout_idx=null,this.svg={},this.layout=at(t||{},Ct),this.parent?(this.state=this.parent.state,this._state_id=this.id,this.state[this._state_id]=this.state[this._state_id]||{}):(this.state=null,this._state_id=null),this.data_layers={},this._data_layer_ids_by_z_index=[],this._data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this._zoom_timeout=null,this._event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this._event_hooks[t]&&this._event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=at(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(gt,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id '${t.id}'; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=xe.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this._data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this._data_layer_ids_by_z_index.length+e.layout.z_index,0)),this._data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this._data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id]._layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){const e=this.data_layers[t];if(!e)throw new Error(`Unable to remove data layer, ID not found: ${t}`);return e.destroyAllTooltips(),e.svg.container&&e.svg.container.remove(),this.layout.data_layers.splice(e._layout_idx,1),delete this.state[e._state_id],delete this.data_layers[t],this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id]._layout_idx=e})),this}clearSelections(){return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height);const{cliparea:t}=this.layout,{margin:e}=this.layout;this.inner_border.attr("x",e.left).attr("y",e.top).attr("width",this.parent_plot.layout.width-(e.left+e.right)).attr("height",this.layout.height-(e.top+e.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const s=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),n=Math.pow(10,e);return t===1/0&&(t=n),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,n),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},i={},a=this.layout.axes;if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};a.x.range&&(t.start=a.x.range.start||t.start,t.end=a.x.range.end||t.end),i.x=[t.start,t.end],i.x_shifted=[t.start,t.end]}if(this.y1_extent){const e={start:t.height,end:0};a.y1.range&&(e.start=a.y1.range.start||e.start,e.end=a.y1.range.end||e.end),i.y1=[e.start,e.end],i.y1_shifted=[e.start,e.end]}if(this.y2_extent){const e={start:t.height,end:0};a.y2.range&&(e.start=a.y2.range.start||e.start,e.end=a.y2.range.end||e.end),i.y2=[e.start,e.end],i.y2_shifted=[e.start,e.end]}let{_interaction:n}=this.parent;const o=n.dragging;if(n.panel_id&&(n.panel_id===this.id||n.linked_panel_ids.includes(this.id))){let a,r=null;if(n.zooming&&"function"==typeof this.x_scale){const s=Math.abs(this.x_extent[1]-this.x_extent[0]),o=Math.round(this.x_scale.invert(i.x_shifted[1]))-Math.round(this.x_scale.invert(i.x_shifted[0]));let r=n.zooming.scale;const l=Math.floor(o*(1/r));r<1&&!isNaN(this.parent.layout.max_region_scale)?r=1/(Math.min(l,this.parent.layout.max_region_scale)/o):r>1&&!isNaN(this.parent.layout.min_region_scale)&&(r=1/(Math.max(l,this.parent.layout.min_region_scale)/o));const h=Math.floor(s*r);a=n.zooming.center-e.left-this.layout.origin.x;const c=a/t.width,d=Math.max(Math.floor(this.x_scale.invert(i.x_shifted[0])-(h-o)*c),1);i.x_shifted=[this.x_scale(d),this.x_scale(d+h)]}else if(o)switch(o.method){case"background":i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x;break;case"x_tick":I.event&&I.event.shiftKey?(i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x):(a=o.start_x-e.left-this.layout.origin.x,r=s(a/(a+o.dragged_x),3),i.x_shifted[0]=0,i.x_shifted[1]=Math.max(t.width*(1/r),1));break;case"y1_tick":case"y2_tick":{const n=`y${o.method[1]}_shifted`;I.event&&I.event.shiftKey?(i[n][0]=t.height+o.dragged_y,i[n][1]=+o.dragged_y):(a=t.height-(o.start_y-e.top-this.layout.origin.y),r=s(a/(a-o.dragged_y),3),i[n][0]=t.height,i[n][1]=t.height-t.height*(1/r))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(i[t][0]),this[`${t}_scale`].invert(i[t][1])],this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!I.event.shiftKey&&!I.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(I.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=I.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,I.event.wheelDelta||-I.event.detail||-I.event.deltaY));0!==e&&(this.parent._interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),n=this.parent._interaction,n.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this._zoom_timeout&&clearTimeout(this._zoom_timeout),this._zoom_timeout=setTimeout((()=>{this.parent._interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this._initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{const e=this.layout.axes[t];Object.keys(e).length&&!1!==e.render?(e.render=!0,e.label=e.label||null):e.render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){const s=this.layout;return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),s.height=Math.max(Math.round(+e),s.min_height)),s.cliparea.width=Math.max(this.parent_plot.layout.width-(s.margin.left+s.margin.right),0),s.cliparea.height=Math.max(s.height-(s.margin.top+s.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",s.height),this._initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this._initialized&&this.render(),this}setMargin(t,e,s,i){let a;const{cliparea:n,margin:o}=this.layout;return!isNaN(t)&&t>=0&&(o.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(o.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(o.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(o.left=Math.max(Math.round(+i),0)),o.top+o.bottom>this.layout.height&&(a=Math.floor((o.top+o.bottom-this.layout.height)/2),o.top-=a,o.bottom-=a),o.left+o.right>this.parent_plot.layout.width&&(a=Math.floor((o.left+o.right-this.parent_plot.layout.width)/2),o.left-=a,o.right-=a),["top","right","bottom","left"].forEach((t=>{o[t]=Math.max(o[t],0)})),n.width=Math.max(this.parent_plot.layout.width-(o.left+o.right),0),n.height=Math.max(this.layout.height-(o.top+o.bottom),0),n.origin.x=o.left,n.origin.y=o.top,this._initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=_t.call(this),this.loader=pt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new Pt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new It(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this._data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(I.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent._panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){const{parent:t}=this,e=this.layout.y_index;return t._panel_ids_by_y_index[e-1]&&(t._panel_ids_by_y_index[e]=t._panel_ids_by_y_index[e-1],t._panel_ids_by_y_index[e-1]=this.id,t.applyPanelYIndexesToPanelLayouts(),t.positionPanels()),this}moveDown(){const{_panel_ids_by_y_index:t}=this.parent;return t[this.layout.y_index+1]&&(t[this.layout.y_index]=t[this.layout.y_index+1],t[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this._data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this._data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this._data_promises).then((()=>{this._initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=I.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=I.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this._data_layer_ids_by_z_index.reduce(((e,a)=>{const n=s.data_layers[a];return e.concat(n.getTicks(t,i))}),[]).map((t=>{let s={};return s=at(s,e),at(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,n=1.5,o=.5+1.5*n,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(_=parseFloat(_.toFixed(c)));u.push(_),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eqt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=I.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=I.select(this).select("text");s[`${t}_ticks`][i].style&>(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const n=this.layout.axes[t].label||null;return null!==n&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(Ht(n,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof I.select(this).node().focus&&I.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";I.event&&I.event.shiftKey&&(i="move"),I.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){I.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this._data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this._data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;Dt.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Dt.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Bt={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Ut{constructor(t,e,s){this._initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this._panel_ids_by_y_index=[],this._remap_promises=[],this.layout=s,at(this.layout,Bt),this._base_layout=nt(this.layout),this.state=this.layout.state,this.lzd=new ut(e),this._external_listeners=new Map,this._event_hooks={},this._interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this._event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this._event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Dt(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this._panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this._panel_ids_by_y_index.length+e.layout.y_index,0)),this._panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this._panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id]._layout_idx=s,this._initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t]._data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i._layer_state,delete this.layout.state[i._state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){const e=this.panels[t];if(!e)throw new Error(`Unable to remove panel, ID not found: ${t}`);return this._panel_boundaries.hide(),this.clearPanelData(t),e.loader.hide(),e.toolbar.destroy(!0),e.curtain.hide(),e.svg.container&&e.svg.container.remove(),this.layout.panels.splice(e._layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id]._layout_idx=e})),this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this._initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e){const{from_layer:s,namespace:i,data_operations:a,onerror:n}=t,o=n||function(t){console.error("An error occurred while acting on an external callback",t)};if(s){const t=`${this.getBaseId()}.`,i=s.startsWith(t)?s:`${t}${s}`;let a=!1;for(let t of Object.values(this.panels))if(a=Object.values(t.data_layers).some((t=>t.getBaseId()===i)),a)break;if(!a)throw new Error(`Could not subscribe to unknown data layer ${i}`);const n=t=>{if(t.data.layer===i)try{e(t.data.content,this)}catch(t){o(t)}};return this.on("data_from_layer",n),n}if(!i)throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option");const[r,l]=this.lzd.config_to_sources(i,a),h=()=>{try{this.lzd.getData(this.state,r,l).then((t=>e(t,this))).catch(o)}catch(t){o(t)}};return this.on("data_rendered",h),h}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this._remap_promises=[],this.loading_data=!0;for(let t in this.panels)this._remap_promises.push(this.panels[t].reMap());return Promise.all(this._remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e._data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this._initialized=!1,this.svg=null,this.panels=null}mutateLayout(){Object.values(this.panels).forEach((t=>{Object.values(t.data_layers).forEach((t=>t.mutateLayout()))}))}_canInteract(t){t=t||null;const{_interaction:e}=this;return t?(void 0===e.panel_id||e.panel_id===t)&&!this.loading_data:!(e.dragging||e.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==I.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this._panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");return this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,n=e.layout.height*s;e.setDimensions(a,n),e.setOrigin(0,i),i+=n,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this._initialized&&(this._panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e of Object.values(this.panels))e.layout.interaction.x_linked&&(t.left=Math.max(t.left,e.layout.margin.left),t.right=Math.max(t.right,e.layout.margin.right));let e=0;return this._panel_ids_by_y_index.forEach((s=>{const i=this.panels[s],a=i.layout;if(i.setOrigin(0,e),e+=this.panels[s].layout.height,a.interaction.x_linked){const e=Math.max(t.left-a.margin.left,0)+Math.max(t.right-a.margin.right,0);a.width+=e,a.margin.left=t.left,a.margin.right=t.right,a.cliparea.origin.x=t.left}})),this.setDimensions(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize){I.select(this.container).classed("lz-container-responsive",!0);const t=()=>this.rescaleSVG();if(window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t),"undefined"!=typeof IntersectionObserver){const t={root:document.documentElement,threshold:.9};new IntersectionObserver(((t,e)=>{t.some((t=>t.intersectionRatio>0))&&this.rescaleSVG()}),t).observe(this.container)}const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}if(this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this._mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=_t.call(this),this.loader=pt.call(this),this._panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent._panel_ids_by_y_index.forEach(((t,e)=>{const s=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=I.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent._panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+I.event.dy);const i=t.layout.height-s;this.parent._panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent._panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent._panel_boundaries.selectors.push(s)}));const t=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=I.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+I.event.dx,this.parent._total_height+I.event.dy)})),t.call(e),this.parent._panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent._panel_ids_by_y_index[s]],a=i._getPageOrigin(),n=t.x,o=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${o}px`).style("left",`${n}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&I.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this._panel_boundaries.hide_timeout),this._panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this._panel_boundaries.hide_timeout=setTimeout((()=>{this._panel_boundaries.hide()}),300)})),this.toolbar=new Pt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this._mouse_guide.vertical.attr("x",-1),this._mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=I.mouse(this.svg.node());this._mouse_guide.vertical.attr("x",t[0]),this._mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{const{_interaction:t}=this;if(t.dragging){const e=I.mouse(this.svg.node());I.event&&I.event.preventDefault(),t.dragging.dragged_x=e[0]-t.dragging.start_x,t.dragging.dragged_y=e[1]-t.dragging.start_y,this.panels[t.panel_id].render(),t.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=I.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this._initialized=!0;const a=this.svg.node().getBoundingClientRect(),n=a.width?a.width:this.layout.width,o=a.height?a.height:this._total_height;return this.setDimensions(n,o),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof Dt&&s&&this._canInteract()))return this.stopDrag();const i=I.mouse(this.svg.node());return this._interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){const{_interaction:t}=this;if(!t.dragging)return this;if("object"!=typeof this.panels[t.panel_id])return this._interaction={},this;const e=this.panels[t.panel_id],s=(t,s,i)=>{e._data_layer_ids_by_z_index.forEach((a=>{const n=e.data_layers[a].layout[`${t}_axis`];n.axis===s&&(n.floor=i[0],n.ceiling=i[1],delete n.lower_buffer,delete n.upper_buffer,delete n.min_extent,delete n.ticks)}))};switch(t.dragging.method){case"background":case"x_tick":0!==t.dragging.dragged_x&&(s("x",1,e.x_extent),this.applyState({start:e.x_extent[0],end:e.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==t.dragging.dragged_y){const i=parseInt(t.dragging.method[1]);s("y",i,e[`y${i}_extent`])}}return this._interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function qt(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),n=Math.min(Math.max(e,0),2),o=Math.min(Math.max(a,n),12);let r=`${(t/Math.pow(10,e)).toFixed(o)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Ft(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function Ht(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const n=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(n())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},o=[];for(;i.length>0;)o.push(n());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new K(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return o.map(l).join("")}const Gt=new h;Gt.add("=",((t,e)=>t===e)),Gt.add("!=",((t,e)=>t!=e)),Gt.add("<",((t,e)=>tt<=e)),Gt.add(">",((t,e)=>t>e)),Gt.add(">=",((t,e)=>t>=e)),Gt.add("%",((t,e)=>t%e)),Gt.add("in",((t,e)=>e&&e.includes(t))),Gt.add("match",((t,e)=>t&&t.includes(e)));const Jt=Gt,Zt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Kt=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Wt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Yt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let n=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var n=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(n=i)})),null===n)return a;const t=(+e-s[n-1])/(s[n]-s[n-1]);return isFinite(t)?I.interpolate(i[n-1],i[n])(t):a}};function Qt(t,e){if(void 0===e)return null;const{beta_field:s,stderr_beta_field:i,"+":a=null,"-":n=null}=t;if(!s||!i)throw new Error("effect_direction must specify how to find required 'beta' and 'stderr_beta' fields");const o=e[s],r=e[i];if(void 0!==o)if(void 0!==r){if(o-1.96*r>0)return a;if(o+1.96*r<0)return n||null}else{if(o>0)return a;if(o<0)return n}return null}const te=new h;for(let[t,e]of Object.entries(n))te.add(t,e);te.add("if",Zt);const ee=te,se={id:"",type:"",tag:"custom_data_type",namespace:{},data_operations:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class ie{constructor(t,e){this._initialized=!1,this._layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=at(t||{},se),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=nt(this.layout),this.state={},this._state_id=null,this._layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this._tooltips={}),this._global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1},this._data_contract=new Set,this._entities=new Map,this._dependencies=[],this.mutateLayout()}render(){throw new Error("Method must be implemented")}moveForward(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e+1]&&(t[e]=t[e+1],t[e+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e-1]&&(t[e]=t[e-1],t[e-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this._layer_state.extra_fields[i]||(this._layer_state.extra_fields[i]={}),this._layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}mutateLayout(){if(this.parent_plot){const{namespace:t,data_operations:e}=this.layout;this._data_contract=rt(this.layout,Object.keys(t));const[s,i]=this.parent_plot.lzd.config_to_sources(t,e,this);this._entities=s,this._dependencies=i}}_getDataExtent(t,e){return t=t||this.data,I.extent(t,(t=>+new K(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field;let i=t[s];if(void 0===i&&/{{[^{}]*}}/.test(s)&&(i=Ht(s,t,{})),null==i)throw new Error("Unable to generate element ID");const a=i.toString().replace(/\W/g,""),n=`${this.getBaseId()}-${a}`.replace(/([:.[\],])/g,"_");return t[e]=n,n}getElementStatusNodeId(t){return null}getElementById(t){const e=I.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Jt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new K(t):null;if(this.data.length&&this._data_contract.size){const t=new Set(this._data_contract);for(let e of this.data)if(Object.keys(e).forEach((e=>t.delete(e))),!t.size)break;t.size&&console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...t]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`)}return this.data.forEach(((a,n)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(p+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=_<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-_,0),e=Math.max(c.width/2+_-u,0);y=h.x+_-c.width/2-e+t,b=h.x+_-y-7,"top"===w?(g=h.y+p-(v+c.height+8),f="down",m=c.height-1):(g=h.y+p+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+_+x+8,f="left",b=-8):(y=h.x+_-c.width-x-8,f="right",b=c.width-1),p-c.height/2<=0?(g=h.y+p-10.5-6,m=6):p+c.height/2>=d?(g=h.y+p+7+6-c.height,m=c.height-14-6):(g=h.y+p-c.height/2,m=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${f}`).style("left",`${b}px`).style("top",`${m}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:n}=t,o=Jt.get(i),r=this.getElementAnnotation(e);o(s?new K(s).resolve(e,r):e,n)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this._layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;C.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this._state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this._state_id]=t),this._layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this._tooltips[e])return this._tooltips[e]={data:t,arrow:null,selector:I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this._layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this._tooltips[e].selector.html(""),this._tooltips[e].arrow=null,this.layout.tooltip.html&&this._tooltips[e].selector.html(Ht(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this._tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this._tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this._tooltips[s]&&("object"==typeof this._tooltips[s].selector&&this._tooltips[s].selector.remove(),delete this._tooltips[s]),!e){this._layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this._tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this._tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this._tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this._tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){const s=this.layout.tooltip;if("object"!=typeof s)return this;const i=this.getElementId(t),a=(t,e,s)=>{let i=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",i=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let n;for(let o in e)n=a(t,e[o],o),null===i?i=n:"and"===s?i=i&&n:"or"===s&&(i=i||n)}}return i};let n={};"string"==typeof s.show?n={and:[s.show]}:"object"==typeof s.show&&(n=s.show);let o={};"string"==typeof s.hide?o={and:[s.hide]}:"object"==typeof s.hide&&(o=s.hide);const r=this._layer_state;var l={};C.adjectives.forEach((t=>{const e=`un${t}`;l[t]=r.status_flags[t].has(i),l[e]=!l[t]}));const h=a(l,n),c=a(l,o),d=r.status_flags.has_tooltip.has(i);return!h||!e&&!d||c?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),I.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const n=this.getElementStatusNodeId(e);null!==n&&I.select(`#${n}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const o=!this._layer_state.status_flags[t].has(a);s&&o&&this._layer_state.status_flags[t].add(a),s||o||this._layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,o),o&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!o&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!o&&s||this.parent.emit("match_requested",{value:new K(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this._layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this._layer_state.status_flags[t]=new Set}return this._global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||I.select(I.event.target).datum(),s===!!I.event.ctrlKey&&i===!!I.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a._layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=Ht(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this._layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e._state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this._entities,this._dependencies).then((t=>{this.data=t,this.applyDataMethods(),this._initialized=!0,this.parent.emit("data_from_layer",{layer:this.getBaseId(),content:nt(t)},!0)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;ie.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},ie.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},ie.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},ie.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const ae={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class ne extends ie{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");at(t,ae),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],n=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+n)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const oe={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class re extends ie{constructor(t){if(at(t,oe),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions.length&&t.namespace&&Object.keys(t.namespace).length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>I.ascending(t[s],e[s])||I.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,n){const o=a[a.length-1]||t;if(t[s]===o[s]&&t[i]<=o[e]){const s=Math.min(o[i],t[i]),n=Math.max(o[e],t[e]);t=Object.assign({},o,t,{[i]:s,[e]:n}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const le={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class he extends ie{constructor(t){t=at(t,le),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function n(t){const a=t[e.x_axis.field1],n=t[e.x_axis.field2],o=(a+n)/2,r=[[s(a),i(0)],[s(o),i(t[e.y_axis.field])],[s(n),i(0)]];return I.line().x((t=>t[0])).y((t=>t[1])).curve(I.curveNatural)(r)}const o=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(gt,e.style),o.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(o).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>n(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>n(t))),r.exit().remove(),o.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],n=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:n(t.data[s.y_axis.field]),y_max:n(0)}}}const ce={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:15,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class de extends ie{constructor(t){t=at(t,ce),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=I.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const n=I.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,n.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(n).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),n.exit().remove();const o=I.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));o.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(o).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),o.exit().remove();const r=I.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=I.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=I.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const ue={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5,tooltip:null};class _e extends ie{constructor(t){if((t=at(t,ue)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const n=t.x_scale,o=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?I.area().x((t=>+n(t[e]))).y0(+o(0)).y1((t=>+o(t[s]))):I.line().x((t=>+n(t[e]))).y((t=>+o(t[s]))).curve(I[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(gt,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;void 0===e&&(e=!0),this._global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this._global_statuses).forEach((t=>{this._global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const pe={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class ge extends ie{constructor(t){t=at(t,pe),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments)}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],n=I.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const n=+t[e](s.y);return isNaN(n)?a[i]:n}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",n).call(gt,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=I.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ye={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class fe extends ie{constructor(t){(t=at(t,ye)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),n=Math.sqrt(a/Math.PI);return{x_min:e-n,x_max:e+n,y_min:i-n,y_max:i+n}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,n=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,o=(t,a)=>{const n=+t.attr("x"),o=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",n-o),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",n+o),i&&a.attr("x2",r+l))};t._label_texts.each((function(e,a){const r=I.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>n){const e=i?I.select(t._label_lines.nodes()[a]):null;o(r,e)}})),t._label_texts.each((function(e,n){const r=I.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?I.select(t._label_lines.nodes()[n]):null;t._label_texts.each((function(){const t=I.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(o(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=o.attr("y"),c=.5*(r.topp?(g=d-+n,d=+n,u-=g):u+l.height/2>p&&(g=u-+h,u=+h,d-=g),a.attr("y",d),o.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t._label_texts.nodes();t._label_lines.attr("y2",((t,s)=>I.select(e[s]).attr("y")))}this._label_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let n=this._applyFilters();if(n.forEach((t=>{let n=e(t[this.layout.x_axis.field]),o=s(t[this.layout.y_axis.field]);isNaN(n)&&(n=-1e3),isNaN(o)&&(o=-1e3),t[i]=n,t[a]=o})),this.layout.coalesce.active&&n.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:o,x_gap:r,y_gap:l}=this.layout.coalesce;n=function(t,e,s,i,a,n,o){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function _(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function p(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],f=g>=e&&g<=s&&y>=a&&y<=n;t.lz_is_match||!f?(_(),r.push(t)):null===c?p(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=o?u.push(t):(_(),p(g,y,t))})),_(),r}(n,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(o)?s(+o):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=n.filter(t)}else e=n;this._label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const o=`lz-data_layer-${this.layout.type}-label`,r=this._label_groups.enter().append("g").attr("class",o);this._label_texts&&this._label_texts.remove(),this._label_texts=this._label_groups.merge(r).append("text").text((e=>Ht(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(gt,t.layout.label.style||{}),t.layout.label.lines&&(this._label_lines&&this._label_lines.remove(),this._label_lines=this._label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(gt,t.layout.label.lines.style||{})),this._label_groups.exit().remove()}else this._label_texts&&this._label_texts.remove(),this._label_lines&&this._label_lines.remove(),this._label_groups&&this._label_groups.remove();const o=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(n,(t=>t[this.layout.id_field])),r=I.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>ot(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;o.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(o).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),o.exit().remove(),this.layout.label&&(this.flip_labels(),this._label_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=I.select(I.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class me extends fe{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],n="string"==typeof i?i.toLowerCase():i,o="string"==typeof a?a.toLowerCase():a;return n===o?0:n{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],n=i[e],o=s[a]||[n,n];s[a]=[Math.min(o[0],n),Math.max(o[1],n)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const o=i[t];let r;switch(s){case"left":r=o[0];break;case"center":const t=o[1]-o[0];r=o[0]+(0!==t?t:o[0])/2;break;case"right":r=o[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const be=new c;for(let[t,e]of Object.entries(o))be.add(t,e);const xe=be,ve=7.301,we={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{assoc:variant|htmlescape}}
    \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
    \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
    '},$e=function(){const t=nt(we);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),ze={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

    {{gene_name|htmlescape}}

    Gene ID: {{gene_id|htmlescape}}
    Transcript ID: {{transcript_id|htmlescape}}
    {{#if pLI}}
    ConstraintExpected variantsObserved variantsConst. Metric
    Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
    o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
    Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
    o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
    pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
    o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

    {{/if}}More data on gnomAD'},ke={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{catalog:variant|htmlescape}}
    Catalog entries: {{n_catalog_matches|htmlescape}}
    Top Trait: {{catalog:trait|htmlescape}}
    Top P Value: {{catalog:log_pvalue|logtoscinotation}}
    More: GWAS catalog / dbSNP'},Ee={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
    {{access:start1|htmlescape}}-{{access:end1|htmlescape}}
    Promoter
    {{access:start2|htmlescape}}-{{access:end2|htmlescape}}
    {{#if access:target}}Target: {{access:target|htmlescape}}
    {{/if}}Score: {{access:score|htmlescape}}"},Me={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:ve},Se={id:"recombrate",namespace:{recomb:"recomb"},data_operations:[{type:"fetch",from:["recomb"]}],type:"line",tag:"recombination",z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"recomb:position"},y_axis:{axis:2,field:"recomb:recomb_rate",floor:0,ceiling:100}},Ne={namespace:{assoc:"assoc",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)"]},{type:"left_match",name:"assoc_plus_ld",requires:["assoc","ld"],params:["assoc:position","ld:position2"]}],id:"associationpvalues",type:"scatter",tag:"association",id_field:"assoc:variant",coalesce:{active:!0},point_shape:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"diamond"}},{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"assoc:beta",stderr_beta_field:"assoc:se"}},"circle"],point_size:{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:80,else:40}},color:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"#9632b8"}},{scale_function:"numerical_bin",field:"ld:correlation",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{label:"LD (r²)",label_size:14},{shape:"ribbon",orientation:"vertical",width:10,height:15,color_stops:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"],tick_labels:[0,.2,.4,.6,.8,1]}],label:null,z_index:2,x_axis:{field:"assoc:position"},y_axis:{axis:1,field:"assoc:log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(we)},Ae={id:"coaccessibility",type:"arcs",tag:"coaccessibility",namespace:{access:"access"},data_operations:[{type:"fetch",from:["access"]}],match:{send:"access:target",receive:"access:target"},id_field:"{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}",filters:[{field:"access:score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"access:start1",field2:"access:start2"},y_axis:{axis:1,field:"access:score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(Ee)},Oe=function(){let t=nt(Ne);return t=at({id:"associationpvaluescatalog",fill_opacity:.7},t),t.data_operations.push({type:"assoc_to_gwas_catalog",name:"assoc_catalog",requires:["assoc_plus_ld","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}),t.tooltip.html+='{{#if catalog:rsid}}
    See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t}(),Te={id:"phewaspvalues",type:"category_scatter",tag:"phewas",namespace:{phewas:"phewas"},data_operations:[{type:"fetch",from:["phewas"]}],point_shape:[{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"phewas:beta",stderr_beta_field:"phewas:se"}},"circle"],point_size:70,tooltip_positioning:"vertical",id_field:"{{phewas:trait_group}}_{{phewas:trait_label}}",x_axis:{field:"lz_auto_x",category_field:"phewas:trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"phewas:log_pvalue",floor:0,upper_buffer:.15},color:[{field:"phewas:trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Trait: {{phewas:trait_label|htmlescape}}
    \nTrait Category: {{phewas:trait_group|htmlescape}}
    \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
    β: {{phewas:beta|scinotation|htmlescape}}
    {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}"},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{phewas:trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"phewas:log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},Le={namespace:{gene:"gene",constraint:"constraint"},data_operations:[{type:"fetch",from:["gene","constraint(gene)"]},{name:"gene_constraint",type:"genes_to_gnomad_constraint",requires:["gene","constraint"]}],id:"genes",type:"genes",tag:"genes",id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ze)},je=at({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},nt(Le)),Pe={namespace:{assoc:"assoc",catalog:"catalog"},data_operations:[{type:"fetch",from:["assoc","catalog"]},{type:"assoc_to_gwas_catalog",name:"assoc_plus_ld",requires:["assoc","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}],id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:"#0000CC",filters:[{field:"catalog:rsid",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ke),tooltip_positioning:"top"},Re={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},Ie={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},Ce={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},De={widgets:[{type:"title",title:"LocusZoom",subtitle:`v${l}`,position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Be=function(){const t=nt(De);return t.widgets.push(nt(Re)),t}(),Ue=function(){const t=nt(De);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),qe={id:"association",tag:"association",min_height:200,height:300,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:50},y2:{label:"Recombination Rate (cM/Mb)",label_offset:46}},legend:{orientation:"vertical",origin:{x:75,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Me),nt(Se),nt(Ne)]},Fe={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:40,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Ae)]},He=function(){let t=nt(qe);return t=at({id:"associationcatalog"},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{catalog:trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"catalog:trait",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve},{field:"ld:correlation",operator:">",value:.4}],style:{"font-size":"12px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[nt(Me),nt(Se),nt(Oe)],t}(),Ge={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:55,bottom:20,left:70},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},nt(Ie)),t}(),data_layers:[nt(je)]},Je={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:55,bottom:120,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:50}},data_layers:[nt(Me),nt(Te)]},Ze={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:55,bottom:10,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Pe)]},Ke={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[nt(qe),nt(Ge)]},Ve={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[Ze,He,Ge]},We={width:800,responsive_resize:!0,toolbar:De,panels:[nt(Je),at({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"}}},nt(Ge))],mouse_guide:!1},Ye={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:nt(De),panels:[nt(Fe),function(){const t=Object.assign({height:270},nt(Ge)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},Xe={standard_association:we,standard_association_with_label:$e,standard_genes:ze,catalog_variant:ke,coaccessibility:Ee},Qe={ldlz2_pop_selector:Re,gene_selector_menu:Ie},ts={standard_panel:Ce,standard_plot:De,standard_association:Be,region_nav_plot:Ue},es={significance:Me,recomb_rate:Se,association_pvalues:Ne,coaccessibility:Ae,association_pvalues_catalog:Oe,phewas_pvalues:Te,genes:Le,genes_filtered:je,annotation_catalog:Pe},ss={association:qe,coaccessibility:Fe,association_catalog:He,genes:Ge,phewas:Je,annotation_catalog:Ze},is={standard_association:Ke,association_catalog:Ve,standard_phewas:We,coaccessibility:Ye};const as=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);const a=s.namespace;i.namespace||delete s.namespace;let n=at(s,i);return a&&(n=it(n,a)),nt(n)}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=nt(s);return"data_layer"===t&&a.namespace&&(a._auto_fields=[...rt(a,Object.keys(a.namespace))].sort()),super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return at(t,e)}renameField(){return lt(...arguments)}mutate_attrs(){return ht(...arguments)}query_attrs(){return ct(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))as.add(t,s,i);const ns=as,os=new h;function rs(t){return(e,s,...i)=>{if(2!==s.length)throw new Error("Join functions must receive exactly two recordsets");return t(...s,...i)}}os.add("left_match",rs(x)),os.add("inner_match",rs((function(t,e,s,i){return b("inner",...arguments)}))),os.add("full_outer_match",rs((function(t,e,s,i){return b("outer",...arguments)}))),os.add("assoc_to_gwas_catalog",rs((function(t,e,s,i,a){if(!t.length)return t;const n=m(e,i),o=[];for(let t of n.values()){let e,s=0;for(let i of t){const t=i[a];t>=s&&(e=i,s=t)}e.n_catalog_matches=t.length,o.push(e)}return x(t,o,s,i)}))),os.add("genes_to_gnomad_constraint",rs((function(t,e){return t.forEach((function(t){const s=`_${t.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=e[s]&&e[s].gnomad_constraint;i&&Object.keys(i).forEach((function(e){let s=i[e];void 0===t[e]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),t[e]=s)}))})),t})));const ls=os;const hs={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return I.select(t).html(""),I.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!I.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Ut(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Ft(s[2]),e=Ft(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Ft(s[2]),end:Ft(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Ft(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=I.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(gt,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||R}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:R,DataLayers:xe,DataFunctions:ls,Layouts:ns,MatchFunctions:Jt,ScaleFunctions:ee,TransformationFunctions:Z,Widgets:jt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),R}},cs=[];hs.use=function(t,...e){if(!cs.includes(t)){if(e.unshift(hs),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}cs.push(t)}};const ds=hs})(),LocusZoom=i.default})(); +/*! Locuszoom 0.14.0-beta.3 */ +var LocusZoom;(()=>{var t={483:(t,e,s)=>{"use strict";const i=s(919);t.exports=function(t,...e){if(!t){if(1===e.length&&e[0]instanceof Error)throw e[0];throw new i(e)}}},919:(t,e,s)=>{"use strict";const i=s(820);t.exports=class extends Error{constructor(t){super(t.filter((t=>""!==t)).map((t=>"string"==typeof t?t:t instanceof Error?t.message:i(t))).join(" ")||"Unknown error"),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e.assert)}}},820:t=>{"use strict";t.exports=function(...t){try{return JSON.stringify.apply(null,t)}catch(t){return"[Cannot display object: "+t.message+"]"}}},681:(t,e,s)=>{"use strict";const i=s(483),a={};e.B=class{constructor(){this._items=[],this.nodes=[]}add(t,e){const s=[].concat((e=e||{}).before||[]),a=[].concat(e.after||[]),n=e.group||"?",o=e.sort||0;i(!s.includes(n),`Item cannot come before itself: ${n}`),i(!s.includes("?"),"Item cannot come before unassociated items"),i(!a.includes(n),`Item cannot come after itself: ${n}`),i(!a.includes("?"),"Item cannot come after unassociated items"),Array.isArray(t)||(t=[t]);for(const e of t){const t={seq:this._items.length,sort:o,before:s,after:a,group:n,node:e};this._items.push(t)}if(!e.manual){const t=this._sort();i(t,"item","?"!==n?`added into group ${n}`:"","created a dependencies error")}return this.nodes}merge(t){Array.isArray(t)||(t=[t]);for(const e of t)if(e)for(const t of e._items)this._items.push(Object.assign({},t));this._items.sort(a.mergeSort);for(let t=0;tt.sort===e.sort?0:t.sort{function e(t){if("string"==typeof t.source.flags)return t.source.flags;var e=[];return t.global&&e.push("g"),t.ignoreCase&&e.push("i"),t.multiline&&e.push("m"),t.sticky&&e.push("y"),t.unicode&&e.push("u"),e.join("")}t.exports=function t(s){if("function"==typeof s)return s;var i=Array.isArray(s)?[]:{};for(var a in s){var n=s[a],o={}.toString.call(n).slice(8,-1);i[a]="Array"==o||"Object"==o?t(n):"Date"==o?new Date(n.getTime()):"RegExp"==o?RegExp(n.source,e(n)):n}return i}}},e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={exports:{}};return t[i](a,a.exports,s),a.exports}s.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return s.d(e,{a:e}),e},s.d=(t,e)=>{for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),s.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";s.d(i,{default:()=>ds});var t={};s.r(t),s.d(t,{AssociationLZ:()=>M,BaseAdapter:()=>$,BaseApiAdapter:()=>z,BaseLZAdapter:()=>k,BaseUMAdapter:()=>E,GeneConstraintLZ:()=>A,GeneLZ:()=>N,GwasCatalogLZ:()=>S,LDServer:()=>O,PheWASLZ:()=>j,RecombLZ:()=>T,StaticSource:()=>L});var e={};s.r(e),s.d(e,{htmlescape:()=>F,is_numeric:()=>H,log10:()=>D,logtoscinotation:()=>U,neglog10:()=>B,scinotation:()=>q,urlencode:()=>G});var a={};s.r(a),s.d(a,{BaseWidget:()=>yt,_Button:()=>ft,display_options:()=>Ot,download:()=>vt,download_png:()=>wt,filter_field:()=>xt,menu:()=>St,move_panel_down:()=>kt,move_panel_up:()=>zt,region_scale:()=>bt,remove_panel:()=>$t,resize_to_data:()=>Nt,set_state:()=>Tt,shift_region:()=>Et,title:()=>mt,toggle_legend:()=>At,zoom_region:()=>Mt});var n={};s.r(n),s.d(n,{categorical_bin:()=>Vt,effect_direction:()=>Qt,if_value:()=>Zt,interpolate:()=>Xt,numerical_bin:()=>Kt,ordinal_cycle:()=>Wt,stable_choice:()=>Yt});var o={};s.r(o),s.d(o,{BaseDataLayer:()=>ie,annotation_track:()=>ne,arcs:()=>he,category_scatter:()=>me,genes:()=>de,highlight_regions:()=>re,line:()=>_e,orthogonal_line:()=>ge,scatter:()=>fe});var r={};s.r(r),s.d(r,{data_layer:()=>es,panel:()=>ss,plot:()=>is,toolbar:()=>ts,toolbar_widgets:()=>Qe,tooltip:()=>Xe});const l="0.14.0-beta.3";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}class d{constructor(t,e,s={},i=null,a=null){this.key=t,this.value=e,this.metadata=s,this.prev=i,this.next=a}}class u{constructor(t=3){if(this._max_size=t,this._cur_size=0,this._store=new Map,this._head=null,this._tail=null,null===t||t<0)throw new Error('Cache "max_size" must be >= 0')}has(t){return this._store.has(t)}get(t){const e=this._store.get(t);return e?(this._head!==e&&this.add(t,e.value),e.value):null}add(t,e,s={}){if(0===this._max_size)return;const i=this._store.get(t);i&&this._remove(i);const a=new d(t,e,s,null,this._head);if(this._head?this._head.prev=a:this._tail=a,this._head=a,this._store.set(t,a),this._max_size>=0&&this._cur_size>=this._max_size){const t=this._tail;this._tail=this._tail.prev,this._remove(t)}this._cur_size+=1}clear(){this._head=null,this._tail=null,this._cur_size=0,this._store=new Map}remove(t){const e=this._store.get(t);return!!e&&(this._remove(e),!0)}_remove(t){null!==t.prev?t.prev.next=t.next:this._head=t.next,null!==t.next?t.next.prev=t.prev:this._tail=t.prev,this._store.delete(t.key),this._cur_size-=1}find(t){let e=this._head;for(;e;){const s=e.next;if(t(e))return e;e=s}}}var _=s(957),p=s.n(_);function g(t){return"object"!=typeof t?t:p()(t)}var y=s(681);function f(t,e,s,i=!0){if(!s.length)return[];const a=s.map((t=>function(t){const e=/^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(t);if(!e)throw new Error(`Unable to parse dependency specification: ${t}`);let{name_alone:s,name_deps:i,deps:a}=e.groups;return s?[s,[]]:(a=a.split(/\s*,\s*/),[i,a])}(t))),n=new Map(a),o=new y.B;for(let[t,e]of n.entries())try{o.add(t,{after:e,group:t})}catch(e){throw new Error(`Invalid or possible circular dependency specification for: ${t}`)}const r=o.nodes,l=new Map;for(let s of r){const i=e.get(s);if(!i)throw new Error(`Data has been requested from source '${s}', but no matching source was provided`);const a=n.get(s)||[],o=Promise.all(a.map((t=>l.get(t)))).then((e=>{const a=Object.assign({_provider_name:s},t);return i.getData(a,...e)}));l.set(s,o)}return Promise.all([...l.values()]).then((t=>i?t[t.length-1]:t))}function m(t,e){const s=new Map;for(let i of t){const t=i[e];if(void 0===t)throw new Error(`All records must specify a value for the field "${e}"`);if("object"==typeof t)throw new Error("Attempted to group on a field with non-primitive values");let a=s.get(t);a||(a=[],s.set(t,a)),a.push(i)}return s}function b(t,e,s,i,a){const n=m(s,a),o=[];for(let s of e){const e=s[i],a=n.get(e)||[];a.length?o.push(...a.map((t=>Object.assign({},g(t),g(s))))):"inner"!==t&&o.push(g(s))}if("outer"===t){const t=m(e,i);for(let e of s){const s=e[a];(t.get(s)||[]).length||o.push(g(e))}}return o}function x(t,e,s,i){return b("left",...arguments)}const v=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function w(t,e=!1){const s=t&&t.match(v);if(s)return s.slice(1);if(e)return null;throw new Error(`Could not understand marker format for ${t}. Should be of format chr:pos or chr:pos_ref/alt`)}class ${constructor(){throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.')}}class z extends ${}class k extends class extends class{constructor(t={}){this._config=t;const{cache_enabled:e=!0,cache_size:s=3}=t;this._enable_cache=e,this._cache=new u(s)}_buildRequestOptions(t,e){return Object.assign({},t)}_getCacheKey(t){if(this._enable_cache)throw new Error("Method not implemented");return null}_performRequest(t){throw new Error("Not implemented")}_normalizeResponse(t,e){return t}_annotateRecords(t,e){return t}_postProcessResponse(t,e){return t}getData(t={},...e){t=this._buildRequestOptions(t,...e);const s=this._getCacheKey(t);let i;return this._enable_cache&&this._cache.has(s)?i=this._cache.get(s):(i=Promise.resolve(this._performRequest(t)).then((e=>this._normalizeResponse(e,t))),this._cache.add(s,i,t._cache_meta),i.catch((t=>this._cache.remove(s)))),i.then((t=>g(t))).then((e=>this._annotateRecords(e,t))).then((e=>this._postProcessResponse(e,t)))}}{constructor(t={}){super(t),this._url=t.url}_getCacheKey(t){return this._getURL(t)}_getURL(t){return this._url}_performRequest(t){const e=this._getURL(t);if(!this._url)throw new Error('Web based resources must specify a resource URL as option "url"');return fetch(e).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}_normalizeResponse(t,e){return"string"==typeof t?JSON.parse(t):t}}{constructor(t={}){t.params&&(console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'),Object.assign(t,t.params||{}),delete t.params),super(t);const{prefix_namespace:e=!0,limit_fields:s}=t;this._prefix_namespace=e,this._limit_fields=!!s&&new Set(s)}_getCacheKey(t){let{chr:e,start:s,end:i}=t;const a=this._cache.find((({metadata:t})=>e===t.chr&&s>=t.start&&i<=t.end));return a&&({chr:e,start:s,end:i}=a.metadata),t._cache_meta={chr:e,start:s,end:i},`${e}_${s}_${i}`}_postProcessResponse(t,e){if(!this._prefix_namespace||!Array.isArray(t))return t;const{_limit_fields:s}=this,{_provider_name:i}=e;return t.map((t=>Object.entries(t).reduce(((t,[e,a])=>(s&&!s.has(e)||(t[`${i}:${e}`]=a),t)),{})))}_findPrefixedKey(t,e){const s=new RegExp(`:${e}$`),i=Object.keys(t).find((t=>s.test(t)));if(!i)throw new Error(`Could not locate the required key name: ${e} in dependent data`);return i}}class E extends k{constructor(t={}){super(t),this._genome_build=t.genome_build||t.build}_validateBuildSource(t,e){if(t&&e||!t&&!e)throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`)}_normalizeResponse(t,e){let s=super._normalizeResponse(...arguments);if(s=s.data||s,Array.isArray(s))return s;const i=Object.keys(s),a=s[i[0]].length;if(!i.every((function(t){return s[t].length===a})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const n=[],o=Object.keys(s);for(let t=0;t25||"GRCh38"===s)return Promise.resolve([]);e=`{${e.join(" ")} }`;const i=this._getURL(t),a=JSON.stringify({query:e});return fetch(i,{method:"POST",body:a,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}_normalizeResponse(t){if("string"!=typeof t)return t;return JSON.parse(t).data}}class O extends E{constructor(t){t.limit_fields||(t.limit_fields=["variant2","position2","correlation"]),super(t)}__find_ld_refvar(t,e){const s=this._findPrefixedKey(e[0],"variant"),i=this._findPrefixedKey(e[0],"log_pvalue");let a,n={};if(t.ldrefvar)a=t.ldrefvar,n=e.find((t=>t[s]===a))||{};else{let t=0;for(let o of e){const{[s]:e,[i]:r}=o;r>t&&(t=r,a=e,n=o)}}n.lz_is_ld_refvar=!0;const o=w(a,!0);if(!o)throw new Error("Could not request LD for a missing or incomplete marker format");const[r,l,h,c]=o;a=`${r}:${l}`,h&&c&&(a+=`_${h}/${c}`);const d=+l;return d&&t.ldrefvar&&t.chr&&(r!==String(t.chr)||dt.end)?(t.ldrefvar=null,this.__find_ld_refvar(t,e)):a}_buildRequestOptions(t,e){if(!e)throw new Error("LD request must depend on association data");const s=super._buildRequestOptions(...arguments);if(!e.length)return s._skip_request=!0,s;s.ld_refvar=this.__find_ld_refvar(t,e);const i=t.genome_build||this._config.build||"GRCh37";let a=t.ld_source||this._config.source||"1000G";const n=t.ld_pop||this._config.population||"ALL";return"1000G"===a&&"GRCh38"===i&&(a="1000G-FRZ09"),this._validateBuildSource(i,null),Object.assign({},s,{genome_build:i,ld_source:a,ld_population:n})}_getURL(t){const e=this._config.method||"rsquare",{chr:s,start:i,end:a,ld_refvar:n,genome_build:o,ld_source:r,ld_population:l}=t;return[super._getURL(t),"genome_builds/",o,"/references/",r,"/populations/",l,"/variants","?correlation=",e,"&variant=",encodeURIComponent(n),"&chrom=",encodeURIComponent(s),"&start=",encodeURIComponent(i),"&stop=",encodeURIComponent(a)].join("")}_getCacheKey(t){const e=super._getCacheKey(t),{ld_refvar:s,ld_source:i,ld_population:a}=t;return`${e}_${s}_${i}_${a}`}_performRequest(t){if(t._skip_request)return Promise.resolve([]);const e=this._getURL(t);let s={data:{}},i=function(t){return fetch(t).then().then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){s.data[e]=(s.data[e]||[]).concat(t.data[e])})),t.next?i(t.next):s}))};return i(e)}}class T extends E{constructor(t){t.limit_fields||(t.limit_fields=["position","recomb_rate"]),super(t)}_getURL(t){const e=t.genome_build||this._config.build;let s=this._config.source;this._validateBuildSource(e,s);const i=e?`&build=${e}`:` and id in ${s}`;return`${super._getURL(t)}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${i}`}}class L extends k{constructor(t={}){super(...arguments);const{data:e}=t;if(!e||Array.isArray(t))throw new Error("'StaticSource' must provide data as required option 'config.data'");this._data=e}_performRequest(t){return Promise.resolve(this._data)}}class j extends E{_getURL(t){const e=(t.genome_build?[t.genome_build]:null)||this._config.build;if(!e||!Array.isArray(e)||!e.length)throw new Error(["Adapter",this.constructor.name,"requires that you specify array of one or more desired genome build names"].join(" "));return[super._getURL(t),"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",e.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}_getCacheKey(t){return this._getURL(t)}}const P=new c;for(let[e,s]of Object.entries(t))P.add(e,s);P.add("StaticJSON",L),P.add("LDLZ2",O);const R=P,I=d3,C={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function D(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function B(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function U(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function q(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function F(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function H(t){return"number"==typeof t}function G(t){return encodeURIComponent(t)}const J=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,s]of Object.entries(e))J.add(t,s);const Z=J;class K{constructor(t){if(!/^(?:\w+:\w+|^\w+)(?:\|\w+)*$/.test(t))throw new Error(`Invalid field specifier: '${t}'`);const[e,...s]=t.split("|");this.full_name=t,this.field_name=e,this.transformations=s.map((t=>Z.get(t)))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[this.field_name]?s=t[this.field_name]:e&&void 0!==e[this.field_name]&&(s=e[this.field_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const V=/^(\*|[\w]+)/,W=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function Y(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=V.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=V.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=W.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function X(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function Q(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const n=t[s.attr];1===e.length?void 0!==n&&a.push([s.attr]):a.push(...Q(n,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...Q(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...Q(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[n,o]of Object.entries(t))a.push(...Q(o,e).map((t=>[n].concat(t)))),"*"===s.attr&&a.push(...Q(o,i).map((t=>[n].concat(t))))}}else if(s.attrs)for(let[e,n]of Object.entries(t)){const[t,o,r]=X(n,s.attrs);r===s.value&&a.push(...Q(n,i).map((t=>[e].concat(t))))}const n=(o=a,r=JSON.stringify,[...new Map(o.map((t=>[r(t),t]))).values()]);var o,r;return n.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),n}function tt(t,e){const s=function(t,e){let s=[];for(let i of Q(t,e))s.push(X(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=Y(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const et=Math.sqrt(3),st={draw(t,e){const s=-Math.sqrt(e/(3*et));t.moveTo(0,2*-s),t.lineTo(-et*s,s),t.lineTo(et*s,s),t.closePath()}};function it(t,e){if(e=e||{},!t||"object"!=typeof t||"object"!=typeof e)throw new Error("Layout and shared namespaces must be provided as objects");for(let[s,i]of Object.entries(t))"namespace"===s?Object.keys(i).forEach((t=>{const s=e[t];s&&(i[t]=s)})):null!==i&&"object"==typeof i&&(t[s]=it(i,e));return t}function at(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=at(t[s],e[s])):t[s]=nt(e[s])}return t}function nt(t){return JSON.parse(JSON.stringify(t))}function ot(t){if(!t)return null;if("triangledown"===t)return st;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return I[e]||null}function rt(t,e,s=null){const i=new Set;if(!s){if(!e.length)return i;const t=e.join("|");s=new RegExp(`(?:{{)?(?:#if *)?((?:${t}):\\w+)`,"g")}for(const a of Object.values(t)){const t=typeof a;let n=[];if("string"===t){let t;for(;null!==(t=s.exec(a));)n.push(t[1])}else{if(null===a||"object"!==t)continue;n=rt(a,e,s)}for(let t of n)i.add(t)}return i}function lt(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>lt(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,n)=>(a[n]=lt(t[n],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const n=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(n,s)}}function ht(t,e,s){return function(t,e,s){return tt(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function ct(t,e){return function(t,e){return tt(t,e).map((t=>t[2]))}(t,e)}class dt{constructor(t,e,s){this._callable=ls.get(t),this._initiator=e,this._params=s||[]}getData(t,...e){const s={plot_state:t,data_layer:this._initiator};return Promise.resolve(this._callable(s,e,...this._params))}}const ut=class{constructor(t){this._sources=t}config_to_sources(t={},e=[],s){const i=new Map,a=Object.keys(t);let n=e.find((t=>"fetch"===t.type));n||(n={type:"fetch",from:a},e.unshift(n));const o=/^\w+$/;for(let[e,s]of Object.entries(t)){if(!o.test(e))throw new Error(`Invalid namespace name: '${e}'. Must contain only alphanumeric characters`);const t=this._sources.get(s);if(!t)throw new Error(`A data layer has requested an item not found in DataSources: data type '${e}' from ${s}`);i.set(e,t),n.from.find((t=>t.split("(")[0]===e))||n.from.push(e)}let r=Array.from(n.from);for(let t of e){let{type:e,name:a,requires:n,params:o}=t;if("fetch"!==e){let l=0;if(a||(a=t.name=`join${l}`,l+=1),i.has(a))throw new Error(`Configuration error: within a layer, join name '${a}' must be unique`);n.forEach((t=>{if(!i.has(t))throw new Error(`Data operation cannot operate on unknown provider '${t}'`)}));const h=new dt(e,s,o);i.set(a,h),r.push(`${a}(${n.join(", ")})`)}}return[i,r]}getData(t,e,s){return s.length?f(t,e,s,!0):Promise.resolve([])}};function _t(){return{showing:!1,selector:null,content_selector:null,hide_delay:null,show:(t,e)=>(this.curtain.showing||(this.curtain.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&>(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function pt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function gt(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class yt{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&>(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class ft{constructor(t){if(!(t instanceof yt))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),n=this.menu.outer_selector.node().getBoundingClientRect(),o=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-n.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-n.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),_=Math.min(o+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${_}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(gt,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class mt extends yt{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class bt extends yt{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(qt(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&>(this.selector,this.layout.style),this}}class xt extends yt{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class vt extends yt{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=I.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+I.select(this).attr("dy").substring(-2).slice(0,-2);I.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class wt extends vt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((n,o)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{n(URL.createObjectURL(t))}))},r.src=t}))}))}}class $t extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),I.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),I.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class zt extends yt{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class kt extends yt{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot._panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class Et extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${qt(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class Mt extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class St extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class Nt extends yt{constructor(t){super(...arguments)}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class At extends yt{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new ft(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class Ot extends yt{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,n={};s.forEach((t=>{const e=a[t];void 0!==e&&(n[t]=nt(e))})),this._selected_item="default",this.button=new ft(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,o=(a,o,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==o[t];i.layout[t]=e?o[t]:n[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return o(r,n,"default"),a.options.forEach(((t,e)=>o(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class Tt extends yt{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new ft(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,n)=>{const o=s.append("tr"),r=`${e}${n}`;o.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",n).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),o.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const Lt=new c;for(let[t,e]of Object.entries(a))Lt.add(t,e);const jt=Lt;class Pt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{this.addWidget(t)})),"panel"===this.type&&I.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}addWidget(t){try{const e=jt.create(t.type,t,this);return this.widgets.push(e),e}catch(t){console.warn("Failed to create widget"),console.error(t)}}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot._panel_boundaries.dragging||this.parent_plot._interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=I.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=I.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const Rt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:14,hidden:!1};class It{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=at(this.parent.layout.legend||{},Rt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((a=>{const n=this.parent.data_layers[a].layout.legend;Array.isArray(n)&&n.forEach((a=>{const n=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),o=+a.label_size||+this.layout.label_size;let r=0,l=o/2+t/2;i=Math.max(i,o+t);const h=a.shape||"",c=ot(h);if("line"===h){const e=+a.length||16,s=o/4+t/2;n.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(gt,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;n.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(gt,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if("ribbon"===h){const e=+a.width||25,s=+a.height||e,i="horizontal"===(a.orientation||"vertical");let o=a.color_stops;const h=n.append("g"),c=h.append("g"),d=h.append("g");let u=0;if(a.tick_labels){let t;t=i?[0,e*o.length-1]:[s*o.length-1,0];const n=I.scaleLinear().domain(I.extent(a.tick_labels)).range(t),r=(i?I.axisTop:I.axisRight)(n).tickSize(3).tickValues(a.tick_labels).tickFormat((t=>t));d.call(r).attr("class","lz-axis"),u=d.node().getBoundingClientRect().height}i?(d.attr("transform",`translate(0, ${u})`),c.attr("transform",`translate(0, ${u})`)):(h.attr("transform","translate(5, 0)"),d.attr("transform",`translate(${e}, 0)`)),i||(o=o.slice(),o.reverse());for(let t=0;tt&&a>this.parent.parent.layout.width&&(s+=i,e=t,n.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(n)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const Ct={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class Dt{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"!=typeof t.id||!t.id)throw new Error('Panel layouts must specify "id"');if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`);this.id=t.id,this._initialized=!1,this._layout_idx=null,this.svg={},this.layout=at(t||{},Ct),this.parent?(this.state=this.parent.state,this._state_id=this.id,this.state[this._state_id]=this.state[this._state_id]||{}):(this.state=null,this._state_id=null),this.data_layers={},this._data_layer_ids_by_z_index=[],this._data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this._zoom_timeout=null,this._event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this._event_hooks[t]&&this._event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=at(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(gt,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id '${t.id}'; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=xe.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this._data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this._data_layer_ids_by_z_index.length+e.layout.z_index,0)),this._data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this._data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id]._layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){const e=this.data_layers[t];if(!e)throw new Error(`Unable to remove data layer, ID not found: ${t}`);return e.destroyAllTooltips(),e.svg.container&&e.svg.container.remove(),this.layout.data_layers.splice(e._layout_idx,1),delete this.state[e._state_id],delete this.data_layers[t],this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id]._layout_idx=e})),this}clearSelections(){return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height);const{cliparea:t}=this.layout,{margin:e}=this.layout;this.inner_border.attr("x",e.left).attr("y",e.top).attr("width",this.parent_plot.layout.width-(e.left+e.right)).attr("height",this.layout.height-(e.top+e.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const s=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),n=Math.pow(10,e);return t===1/0&&(t=n),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,n),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},i={},a=this.layout.axes;if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};a.x.range&&(t.start=a.x.range.start||t.start,t.end=a.x.range.end||t.end),i.x=[t.start,t.end],i.x_shifted=[t.start,t.end]}if(this.y1_extent){const e={start:t.height,end:0};a.y1.range&&(e.start=a.y1.range.start||e.start,e.end=a.y1.range.end||e.end),i.y1=[e.start,e.end],i.y1_shifted=[e.start,e.end]}if(this.y2_extent){const e={start:t.height,end:0};a.y2.range&&(e.start=a.y2.range.start||e.start,e.end=a.y2.range.end||e.end),i.y2=[e.start,e.end],i.y2_shifted=[e.start,e.end]}let{_interaction:n}=this.parent;const o=n.dragging;if(n.panel_id&&(n.panel_id===this.id||n.linked_panel_ids.includes(this.id))){let a,r=null;if(n.zooming&&"function"==typeof this.x_scale){const s=Math.abs(this.x_extent[1]-this.x_extent[0]),o=Math.round(this.x_scale.invert(i.x_shifted[1]))-Math.round(this.x_scale.invert(i.x_shifted[0]));let r=n.zooming.scale;const l=Math.floor(o*(1/r));r<1&&!isNaN(this.parent.layout.max_region_scale)?r=1/(Math.min(l,this.parent.layout.max_region_scale)/o):r>1&&!isNaN(this.parent.layout.min_region_scale)&&(r=1/(Math.max(l,this.parent.layout.min_region_scale)/o));const h=Math.floor(s*r);a=n.zooming.center-e.left-this.layout.origin.x;const c=a/t.width,d=Math.max(Math.floor(this.x_scale.invert(i.x_shifted[0])-(h-o)*c),1);i.x_shifted=[this.x_scale(d),this.x_scale(d+h)]}else if(o)switch(o.method){case"background":i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x;break;case"x_tick":I.event&&I.event.shiftKey?(i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x):(a=o.start_x-e.left-this.layout.origin.x,r=s(a/(a+o.dragged_x),3),i.x_shifted[0]=0,i.x_shifted[1]=Math.max(t.width*(1/r),1));break;case"y1_tick":case"y2_tick":{const n=`y${o.method[1]}_shifted`;I.event&&I.event.shiftKey?(i[n][0]=t.height+o.dragged_y,i[n][1]=+o.dragged_y):(a=t.height-(o.start_y-e.top-this.layout.origin.y),r=s(a/(a-o.dragged_y),3),i[n][0]=t.height,i[n][1]=t.height-t.height*(1/r))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(i[t][0]),this[`${t}_scale`].invert(i[t][1])],this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!I.event.shiftKey&&!I.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(I.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=I.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,I.event.wheelDelta||-I.event.detail||-I.event.deltaY));0!==e&&(this.parent._interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),n=this.parent._interaction,n.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this._zoom_timeout&&clearTimeout(this._zoom_timeout),this._zoom_timeout=setTimeout((()=>{this.parent._interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this._initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{const e=this.layout.axes[t];Object.keys(e).length&&!1!==e.render?(e.render=!0,e.label=e.label||null):e.render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){const s=this.layout;return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),s.height=Math.max(Math.round(+e),s.min_height)),s.cliparea.width=Math.max(this.parent_plot.layout.width-(s.margin.left+s.margin.right),0),s.cliparea.height=Math.max(s.height-(s.margin.top+s.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",s.height),this._initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this._initialized&&this.render(),this}setMargin(t,e,s,i){let a;const{cliparea:n,margin:o}=this.layout;return!isNaN(t)&&t>=0&&(o.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(o.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(o.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(o.left=Math.max(Math.round(+i),0)),o.top+o.bottom>this.layout.height&&(a=Math.floor((o.top+o.bottom-this.layout.height)/2),o.top-=a,o.bottom-=a),o.left+o.right>this.parent_plot.layout.width&&(a=Math.floor((o.left+o.right-this.parent_plot.layout.width)/2),o.left-=a,o.right-=a),["top","right","bottom","left"].forEach((t=>{o[t]=Math.max(o[t],0)})),n.width=Math.max(this.parent_plot.layout.width-(o.left+o.right),0),n.height=Math.max(this.layout.height-(o.top+o.bottom),0),n.origin.x=o.left,n.origin.y=o.top,this._initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=_t.call(this),this.loader=pt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new Pt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new It(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this._data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(I.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent._panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){const{parent:t}=this,e=this.layout.y_index;return t._panel_ids_by_y_index[e-1]&&(t._panel_ids_by_y_index[e]=t._panel_ids_by_y_index[e-1],t._panel_ids_by_y_index[e-1]=this.id,t.applyPanelYIndexesToPanelLayouts(),t.positionPanels()),this}moveDown(){const{_panel_ids_by_y_index:t}=this.parent;return t[this.layout.y_index+1]&&(t[this.layout.y_index]=t[this.layout.y_index+1],t[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this._data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this._data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this._data_promises).then((()=>{this._initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=I.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=I.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this._data_layer_ids_by_z_index.reduce(((e,a)=>{const n=s.data_layers[a];return e.concat(n.getTicks(t,i))}),[]).map((t=>{let s={};return s=at(s,e),at(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,n=1.5,o=.5+1.5*n,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(_=parseFloat(_.toFixed(c)));u.push(_),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eqt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=I.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=I.select(this).select("text");s[`${t}_ticks`][i].style&>(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const n=this.layout.axes[t].label||null;return null!==n&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(Ht(n,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof I.select(this).node().focus&&I.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";I.event&&I.event.shiftKey&&(i="move"),I.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){I.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this._data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this._data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;Dt.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Dt.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Bt={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Ut{constructor(t,e,s){this._initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this._panel_ids_by_y_index=[],this._remap_promises=[],this.layout=s,at(this.layout,Bt),this._base_layout=nt(this.layout),this.state=this.layout.state,this.lzd=new ut(e),this._external_listeners=new Map,this._event_hooks={},this._interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this._event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this._event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Dt(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this._panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this._panel_ids_by_y_index.length+e.layout.y_index,0)),this._panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this._panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id]._layout_idx=s,this._initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t]._data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i._layer_state,delete this.layout.state[i._state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){const e=this.panels[t];if(!e)throw new Error(`Unable to remove panel, ID not found: ${t}`);return this._panel_boundaries.hide(),this.clearPanelData(t),e.loader.hide(),e.toolbar.destroy(!0),e.curtain.hide(),e.svg.container&&e.svg.container.remove(),this.layout.panels.splice(e._layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id]._layout_idx=e})),this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this._initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e){const{from_layer:s,namespace:i,data_operations:a,onerror:n}=t,o=n||function(t){console.error("An error occurred while acting on an external callback",t)};if(s){const t=`${this.getBaseId()}.`,i=s.startsWith(t)?s:`${t}${s}`;let a=!1;for(let t of Object.values(this.panels))if(a=Object.values(t.data_layers).some((t=>t.getBaseId()===i)),a)break;if(!a)throw new Error(`Could not subscribe to unknown data layer ${i}`);const n=t=>{if(t.data.layer===i)try{e(t.data.content,this)}catch(t){o(t)}};return this.on("data_from_layer",n),n}if(!i)throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option");const[r,l]=this.lzd.config_to_sources(i,a),h=()=>{try{this.lzd.getData(this.state,r,l).then((t=>e(t,this))).catch(o)}catch(t){o(t)}};return this.on("data_rendered",h),h}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this._remap_promises=[],this.loading_data=!0;for(let t in this.panels)this._remap_promises.push(this.panels[t].reMap());return Promise.all(this._remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e._data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this._initialized=!1,this.svg=null,this.panels=null}mutateLayout(){Object.values(this.panels).forEach((t=>{Object.values(t.data_layers).forEach((t=>t.mutateLayout()))}))}_canInteract(t){t=t||null;const{_interaction:e}=this;return t?(void 0===e.panel_id||e.panel_id===t)&&!this.loading_data:!(e.dragging||e.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==I.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this._panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");return this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,n=e.layout.height*s;e.setDimensions(a,n),e.setOrigin(0,i),i+=n,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this._initialized&&(this._panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e of Object.values(this.panels))e.layout.interaction.x_linked&&(t.left=Math.max(t.left,e.layout.margin.left),t.right=Math.max(t.right,e.layout.margin.right));let e=0;return this._panel_ids_by_y_index.forEach((s=>{const i=this.panels[s],a=i.layout;if(i.setOrigin(0,e),e+=this.panels[s].layout.height,a.interaction.x_linked){const e=Math.max(t.left-a.margin.left,0)+Math.max(t.right-a.margin.right,0);a.width+=e,a.margin.left=t.left,a.margin.right=t.right,a.cliparea.origin.x=t.left}})),this.setDimensions(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize){I.select(this.container).classed("lz-container-responsive",!0);const t=()=>this.rescaleSVG();if(window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t),"undefined"!=typeof IntersectionObserver){const t={root:document.documentElement,threshold:.9};new IntersectionObserver(((t,e)=>{t.some((t=>t.intersectionRatio>0))&&this.rescaleSVG()}),t).observe(this.container)}const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}if(this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this._mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=_t.call(this),this.loader=pt.call(this),this._panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent._panel_ids_by_y_index.forEach(((t,e)=>{const s=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=I.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent._panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+I.event.dy);const i=t.layout.height-s;this.parent._panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent._panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent._panel_boundaries.selectors.push(s)}));const t=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=I.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+I.event.dx,this.parent._total_height+I.event.dy)})),t.call(e),this.parent._panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent._panel_ids_by_y_index[s]],a=i._getPageOrigin(),n=t.x,o=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${o}px`).style("left",`${n}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&I.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this._panel_boundaries.hide_timeout),this._panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this._panel_boundaries.hide_timeout=setTimeout((()=>{this._panel_boundaries.hide()}),300)})),this.toolbar=new Pt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this._mouse_guide.vertical.attr("x",-1),this._mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=I.mouse(this.svg.node());this._mouse_guide.vertical.attr("x",t[0]),this._mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{const{_interaction:t}=this;if(t.dragging){const e=I.mouse(this.svg.node());I.event&&I.event.preventDefault(),t.dragging.dragged_x=e[0]-t.dragging.start_x,t.dragging.dragged_y=e[1]-t.dragging.start_y,this.panels[t.panel_id].render(),t.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=I.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this._initialized=!0;const a=this.svg.node().getBoundingClientRect(),n=a.width?a.width:this.layout.width,o=a.height?a.height:this._total_height;return this.setDimensions(n,o),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof Dt&&s&&this._canInteract()))return this.stopDrag();const i=I.mouse(this.svg.node());return this._interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){const{_interaction:t}=this;if(!t.dragging)return this;if("object"!=typeof this.panels[t.panel_id])return this._interaction={},this;const e=this.panels[t.panel_id],s=(t,s,i)=>{e._data_layer_ids_by_z_index.forEach((a=>{const n=e.data_layers[a].layout[`${t}_axis`];n.axis===s&&(n.floor=i[0],n.ceiling=i[1],delete n.lower_buffer,delete n.upper_buffer,delete n.min_extent,delete n.ticks)}))};switch(t.dragging.method){case"background":case"x_tick":0!==t.dragging.dragged_x&&(s("x",1,e.x_extent),this.applyState({start:e.x_extent[0],end:e.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==t.dragging.dragged_y){const i=parseInt(t.dragging.method[1]);s("y",i,e[`y${i}_extent`])}}return this._interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function qt(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),n=Math.min(Math.max(e,0),2),o=Math.min(Math.max(a,n),12);let r=`${(t/Math.pow(10,e)).toFixed(o)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Ft(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function Ht(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const n=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(n())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},o=[];for(;i.length>0;)o.push(n());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new K(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return o.map(l).join("")}const Gt=new h;Gt.add("=",((t,e)=>t===e)),Gt.add("!=",((t,e)=>t!=e)),Gt.add("<",((t,e)=>tt<=e)),Gt.add(">",((t,e)=>t>e)),Gt.add(">=",((t,e)=>t>=e)),Gt.add("%",((t,e)=>t%e)),Gt.add("in",((t,e)=>e&&e.includes(t))),Gt.add("match",((t,e)=>t&&t.includes(e)));const Jt=Gt,Zt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Kt=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Wt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Yt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let n=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var n=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(n=i)})),null===n)return a;const t=(+e-s[n-1])/(s[n]-s[n-1]);return isFinite(t)?I.interpolate(i[n-1],i[n])(t):a}};function Qt(t,e){if(void 0===e)return null;const{beta_field:s,stderr_beta_field:i,"+":a=null,"-":n=null}=t;if(!s||!i)throw new Error("effect_direction must specify how to find required 'beta' and 'stderr_beta' fields");const o=e[s],r=e[i];if(void 0!==o)if(void 0!==r){if(o-1.96*r>0)return a;if(o+1.96*r<0)return n||null}else{if(o>0)return a;if(o<0)return n}return null}const te=new h;for(let[t,e]of Object.entries(n))te.add(t,e);te.add("if",Zt);const ee=te,se={id:"",type:"",tag:"custom_data_type",namespace:{},data_operations:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class ie{constructor(t,e){this._initialized=!1,this._layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=at(t||{},se),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=nt(this.layout),this.state={},this._state_id=null,this._layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this._tooltips={}),this._global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1},this._data_contract=new Set,this._entities=new Map,this._dependencies=[],this.mutateLayout()}render(){throw new Error("Method must be implemented")}moveForward(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e+1]&&(t[e]=t[e+1],t[e+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e-1]&&(t[e]=t[e-1],t[e-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this._layer_state.extra_fields[i]||(this._layer_state.extra_fields[i]={}),this._layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}mutateLayout(){if(this.parent_plot){const{namespace:t,data_operations:e}=this.layout;this._data_contract=rt(this.layout,Object.keys(t));const[s,i]=this.parent_plot.lzd.config_to_sources(t,e,this);this._entities=s,this._dependencies=i}}_getDataExtent(t,e){return t=t||this.data,I.extent(t,(t=>+new K(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field;let i=t[s];if(void 0===i&&/{{[^{}]*}}/.test(s)&&(i=Ht(s,t,{})),null==i)throw new Error("Unable to generate element ID");const a=i.toString().replace(/\W/g,""),n=`${this.getBaseId()}-${a}`.replace(/([:.[\],])/g,"_");return t[e]=n,n}getElementStatusNodeId(t){return null}getElementById(t){const e=I.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Jt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new K(t):null;if(this.data.length&&this._data_contract.size){const t=new Set(this._data_contract);for(let e of this.data)if(Object.keys(e).forEach((e=>t.delete(e))),!t.size)break;t.size&&console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...t]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`)}return this.data.forEach(((a,n)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(p+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=_<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-_,0),e=Math.max(c.width/2+_-u,0);y=h.x+_-c.width/2-e+t,b=h.x+_-y-7,"top"===w?(g=h.y+p-(v+c.height+8),f="down",m=c.height-1):(g=h.y+p+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+_+x+8,f="left",b=-8):(y=h.x+_-c.width-x-8,f="right",b=c.width-1),p-c.height/2<=0?(g=h.y+p-10.5-6,m=6):p+c.height/2>=d?(g=h.y+p+7+6-c.height,m=c.height-14-6):(g=h.y+p-c.height/2,m=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${f}`).style("left",`${b}px`).style("top",`${m}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:n}=t,o=Jt.get(i),r=this.getElementAnnotation(e);o(s?new K(s).resolve(e,r):e,n)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this._layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;C.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this._state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this._state_id]=t),this._layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this._tooltips[e])return this._tooltips[e]={data:t,arrow:null,selector:I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this._layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this._tooltips[e].selector.html(""),this._tooltips[e].arrow=null,this.layout.tooltip.html&&this._tooltips[e].selector.html(Ht(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this._tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this._tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this._tooltips[s]&&("object"==typeof this._tooltips[s].selector&&this._tooltips[s].selector.remove(),delete this._tooltips[s]),!e){this._layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this._tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this._tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this._tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this._tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){const s=this.layout.tooltip;if("object"!=typeof s)return this;const i=this.getElementId(t),a=(t,e,s)=>{let i=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",i=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let n;for(let o in e)n=a(t,e[o],o),null===i?i=n:"and"===s?i=i&&n:"or"===s&&(i=i||n)}}return i};let n={};"string"==typeof s.show?n={and:[s.show]}:"object"==typeof s.show&&(n=s.show);let o={};"string"==typeof s.hide?o={and:[s.hide]}:"object"==typeof s.hide&&(o=s.hide);const r=this._layer_state;var l={};C.adjectives.forEach((t=>{const e=`un${t}`;l[t]=r.status_flags[t].has(i),l[e]=!l[t]}));const h=a(l,n),c=a(l,o),d=r.status_flags.has_tooltip.has(i);return!h||!e&&!d||c?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),I.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const n=this.getElementStatusNodeId(e);null!==n&&I.select(`#${n}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const o=!this._layer_state.status_flags[t].has(a);s&&o&&this._layer_state.status_flags[t].add(a),s||o||this._layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,o),o&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!o&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!o&&s||this.parent.emit("match_requested",{value:new K(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this._layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this._layer_state.status_flags[t]=new Set}return this._global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||I.select(I.event.target).datum(),s===!!I.event.ctrlKey&&i===!!I.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a._layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=Ht(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this._layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e._state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this._entities,this._dependencies).then((t=>{this.data=t,this.applyDataMethods(),this._initialized=!0,this.parent.emit("data_from_layer",{layer:this.getBaseId(),content:nt(t)},!0)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;ie.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},ie.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},ie.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},ie.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const ae={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class ne extends ie{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");at(t,ae),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],n=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+n)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const oe={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class re extends ie{constructor(t){if(at(t,oe),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions.length&&t.namespace&&Object.keys(t.namespace).length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>I.ascending(t[s],e[s])||I.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,n){const o=a[a.length-1]||t;if(t[s]===o[s]&&t[i]<=o[e]){const s=Math.min(o[i],t[i]),n=Math.max(o[e],t[e]);t=Object.assign({},o,t,{[i]:s,[e]:n}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const le={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class he extends ie{constructor(t){t=at(t,le),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function n(t){const a=t[e.x_axis.field1],n=t[e.x_axis.field2],o=(a+n)/2,r=[[s(a),i(0)],[s(o),i(t[e.y_axis.field])],[s(n),i(0)]];return I.line().x((t=>t[0])).y((t=>t[1])).curve(I.curveNatural)(r)}const o=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(gt,e.style),o.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(o).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>n(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>n(t))),r.exit().remove(),o.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],n=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:n(t.data[s.y_axis.field]),y_max:n(0)}}}const ce={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:15,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class de extends ie{constructor(t){t=at(t,ce),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=I.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const n=I.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,n.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(n).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),n.exit().remove();const o=I.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));o.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(o).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),o.exit().remove();const r=I.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=I.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=I.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const ue={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5,tooltip:null};class _e extends ie{constructor(t){if((t=at(t,ue)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const n=t.x_scale,o=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?I.area().x((t=>+n(t[e]))).y0(+o(0)).y1((t=>+o(t[s]))):I.line().x((t=>+n(t[e]))).y((t=>+o(t[s]))).curve(I[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(gt,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;void 0===e&&(e=!0),this._global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this._global_statuses).forEach((t=>{this._global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const pe={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class ge extends ie{constructor(t){t=at(t,pe),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments)}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],n=I.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const n=+t[e](s.y);return isNaN(n)?a[i]:n}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",n).call(gt,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=I.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ye={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class fe extends ie{constructor(t){(t=at(t,ye)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),n=Math.sqrt(a/Math.PI);return{x_min:e-n,x_max:e+n,y_min:i-n,y_max:i+n}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,n=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,o=(t,a)=>{const n=+t.attr("x"),o=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",n-o),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",n+o),i&&a.attr("x2",r+l))};t._label_texts.each((function(e,a){const r=I.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>n){const e=i?I.select(t._label_lines.nodes()[a]):null;o(r,e)}})),t._label_texts.each((function(e,n){const r=I.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?I.select(t._label_lines.nodes()[n]):null;t._label_texts.each((function(){const t=I.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(o(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=o.attr("y"),c=.5*(r.topp?(g=d-+n,d=+n,u-=g):u+l.height/2>p&&(g=u-+h,u=+h,d-=g),a.attr("y",d),o.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t._label_texts.nodes();t._label_lines.attr("y2",((t,s)=>I.select(e[s]).attr("y")))}this._label_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let n=this._applyFilters();if(n.forEach((t=>{let n=e(t[this.layout.x_axis.field]),o=s(t[this.layout.y_axis.field]);isNaN(n)&&(n=-1e3),isNaN(o)&&(o=-1e3),t[i]=n,t[a]=o})),this.layout.coalesce.active&&n.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:o,x_gap:r,y_gap:l}=this.layout.coalesce;n=function(t,e,s,i,a,n,o){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function _(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function p(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],f=g>=e&&g<=s&&y>=a&&y<=n;t.lz_is_match||!f?(_(),r.push(t)):null===c?p(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=o?u.push(t):(_(),p(g,y,t))})),_(),r}(n,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(o)?s(+o):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=n.filter(t)}else e=n;this._label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const o=`lz-data_layer-${this.layout.type}-label`,r=this._label_groups.enter().append("g").attr("class",o);this._label_texts&&this._label_texts.remove(),this._label_texts=this._label_groups.merge(r).append("text").text((e=>Ht(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(gt,t.layout.label.style||{}),t.layout.label.lines&&(this._label_lines&&this._label_lines.remove(),this._label_lines=this._label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(gt,t.layout.label.lines.style||{})),this._label_groups.exit().remove()}else this._label_texts&&this._label_texts.remove(),this._label_lines&&this._label_lines.remove(),this._label_groups&&this._label_groups.remove();const o=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(n,(t=>t[this.layout.id_field])),r=I.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>ot(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;o.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(o).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),o.exit().remove(),this.layout.label&&(this.flip_labels(),this._label_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=I.select(I.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class me extends fe{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],n="string"==typeof i?i.toLowerCase():i,o="string"==typeof a?a.toLowerCase():a;return n===o?0:n{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],n=i[e],o=s[a]||[n,n];s[a]=[Math.min(o[0],n),Math.max(o[1],n)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const o=i[t];let r;switch(s){case"left":r=o[0];break;case"center":const t=o[1]-o[0];r=o[0]+(0!==t?t:o[0])/2;break;case"right":r=o[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const be=new c;for(let[t,e]of Object.entries(o))be.add(t,e);const xe=be,ve=7.301,we={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{assoc:variant|htmlescape}}
    \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
    \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
    '},$e=function(){const t=nt(we);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),ze={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

    {{gene_name|htmlescape}}

    Gene ID: {{gene_id|htmlescape}}
    Transcript ID: {{transcript_id|htmlescape}}
    {{#if pLI}}
    ConstraintExpected variantsObserved variantsConst. Metric
    Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
    o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
    Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
    o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
    pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
    o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

    {{/if}}More data on gnomAD'},ke={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{catalog:variant|htmlescape}}
    Catalog entries: {{n_catalog_matches|htmlescape}}
    Top Trait: {{catalog:trait|htmlescape}}
    Top P Value: {{catalog:log_pvalue|logtoscinotation}}
    More: GWAS catalog / dbSNP'},Ee={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
    {{access:start1|htmlescape}}-{{access:end1|htmlescape}}
    Promoter
    {{access:start2|htmlescape}}-{{access:end2|htmlescape}}
    {{#if access:target}}Target: {{access:target|htmlescape}}
    {{/if}}Score: {{access:score|htmlescape}}"},Me={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:ve},Se={id:"recombrate",namespace:{recomb:"recomb"},data_operations:[{type:"fetch",from:["recomb"]}],type:"line",tag:"recombination",z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"recomb:position"},y_axis:{axis:2,field:"recomb:recomb_rate",floor:0,ceiling:100}},Ne={namespace:{assoc:"assoc",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)"]},{type:"left_match",name:"assoc_plus_ld",requires:["assoc","ld"],params:["assoc:position","ld:position2"]}],id:"associationpvalues",type:"scatter",tag:"association",id_field:"assoc:variant",coalesce:{active:!0},point_shape:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"diamond"}},{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"assoc:beta",stderr_beta_field:"assoc:se"}},"circle"],point_size:{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:80,else:40}},color:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"#9632b8"}},{scale_function:"numerical_bin",field:"ld:correlation",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{label:"LD (r²)",label_size:14},{shape:"ribbon",orientation:"vertical",width:10,height:15,color_stops:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"],tick_labels:[0,.2,.4,.6,.8,1]}],label:null,z_index:2,x_axis:{field:"assoc:position"},y_axis:{axis:1,field:"assoc:log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(we)},Ae={id:"coaccessibility",type:"arcs",tag:"coaccessibility",namespace:{access:"access"},data_operations:[{type:"fetch",from:["access"]}],match:{send:"access:target",receive:"access:target"},id_field:"{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}",filters:[{field:"access:score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"access:start1",field2:"access:start2"},y_axis:{axis:1,field:"access:score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(Ee)},Oe=function(){let t=nt(Ne);return t=at({id:"associationpvaluescatalog",fill_opacity:.7},t),t.data_operations.push({type:"assoc_to_gwas_catalog",name:"assoc_catalog",requires:["assoc_plus_ld","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}),t.tooltip.html+='{{#if catalog:rsid}}
    See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t}(),Te={id:"phewaspvalues",type:"category_scatter",tag:"phewas",namespace:{phewas:"phewas"},data_operations:[{type:"fetch",from:["phewas"]}],point_shape:[{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"phewas:beta",stderr_beta_field:"phewas:se"}},"circle"],point_size:70,tooltip_positioning:"vertical",id_field:"{{phewas:trait_group}}_{{phewas:trait_label}}",x_axis:{field:"lz_auto_x",category_field:"phewas:trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"phewas:log_pvalue",floor:0,upper_buffer:.15},color:[{field:"phewas:trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Trait: {{phewas:trait_label|htmlescape}}
    \nTrait Category: {{phewas:trait_group|htmlescape}}
    \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
    β: {{phewas:beta|scinotation|htmlescape}}
    {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}"},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{phewas:trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"phewas:log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},Le={namespace:{gene:"gene",constraint:"constraint"},data_operations:[{type:"fetch",from:["gene","constraint(gene)"]},{name:"gene_constraint",type:"genes_to_gnomad_constraint",requires:["gene","constraint"]}],id:"genes",type:"genes",tag:"genes",id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ze)},je=at({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},nt(Le)),Pe={namespace:{assoc:"assoc",catalog:"catalog"},data_operations:[{type:"fetch",from:["assoc","catalog"]},{type:"assoc_to_gwas_catalog",name:"assoc_plus_ld",requires:["assoc","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}],id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:"#0000CC",filters:[{field:"catalog:rsid",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ke),tooltip_positioning:"top"},Re={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},Ie={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},Ce={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},De={widgets:[{type:"title",title:"LocusZoom",subtitle:`v${l}`,position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Be=function(){const t=nt(De);return t.widgets.push(nt(Re)),t}(),Ue=function(){const t=nt(De);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),qe={id:"association",tag:"association",min_height:200,height:300,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:50},y2:{label:"Recombination Rate (cM/Mb)",label_offset:46}},legend:{orientation:"vertical",origin:{x:75,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Me),nt(Se),nt(Ne)]},Fe={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:40,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Ae)]},He=function(){let t=nt(qe);return t=at({id:"associationcatalog"},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{catalog:trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"catalog:trait",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve},{field:"ld:correlation",operator:">",value:.4}],style:{"font-size":"12px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[nt(Me),nt(Se),nt(Oe)],t}(),Ge={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:55,bottom:20,left:70},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},nt(Ie)),t}(),data_layers:[nt(je)]},Je={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:55,bottom:120,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:50}},data_layers:[nt(Me),nt(Te)]},Ze={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:55,bottom:10,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Pe)]},Ke={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[nt(qe),nt(Ge)]},Ve={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[Ze,He,Ge]},We={width:800,responsive_resize:!0,toolbar:De,panels:[nt(Je),at({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"}}},nt(Ge))],mouse_guide:!1},Ye={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:nt(De),panels:[nt(Fe),function(){const t=Object.assign({height:270},nt(Ge)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},Xe={standard_association:we,standard_association_with_label:$e,standard_genes:ze,catalog_variant:ke,coaccessibility:Ee},Qe={ldlz2_pop_selector:Re,gene_selector_menu:Ie},ts={standard_panel:Ce,standard_plot:De,standard_association:Be,region_nav_plot:Ue},es={significance:Me,recomb_rate:Se,association_pvalues:Ne,coaccessibility:Ae,association_pvalues_catalog:Oe,phewas_pvalues:Te,genes:Le,genes_filtered:je,annotation_catalog:Pe},ss={association:qe,coaccessibility:Fe,association_catalog:He,genes:Ge,phewas:Je,annotation_catalog:Ze},is={standard_association:Ke,association_catalog:Ve,standard_phewas:We,coaccessibility:Ye};const as=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);const a=s.namespace;i.namespace||delete s.namespace;let n=at(s,i);return a&&(n=it(n,a)),nt(n)}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=nt(s);return"data_layer"===t&&a.namespace&&(a._auto_fields=[...rt(a,Object.keys(a.namespace))].sort()),super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return at(t,e)}renameField(){return lt(...arguments)}mutate_attrs(){return ht(...arguments)}query_attrs(){return ct(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))as.add(t,s,i);const ns=as,os=new h;function rs(t){return(e,s,...i)=>{if(2!==s.length)throw new Error("Join functions must receive exactly two recordsets");return t(...s,...i)}}os.add("left_match",rs(x)),os.add("inner_match",rs((function(t,e,s,i){return b("inner",...arguments)}))),os.add("full_outer_match",rs((function(t,e,s,i){return b("outer",...arguments)}))),os.add("assoc_to_gwas_catalog",rs((function(t,e,s,i,a){if(!t.length)return t;const n=m(e,i),o=[];for(let t of n.values()){let e,s=0;for(let i of t){const t=i[a];t>=s&&(e=i,s=t)}e.n_catalog_matches=t.length,o.push(e)}return x(t,o,s,i)}))),os.add("genes_to_gnomad_constraint",rs((function(t,e){return t.forEach((function(t){const s=`_${t.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=e[s]&&e[s].gnomad_constraint;i&&Object.keys(i).forEach((function(e){let s=i[e];void 0===t[e]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),t[e]=s)}))})),t})));const ls=os;const hs={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return I.select(t).html(""),I.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!I.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Ut(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Ft(s[2]),e=Ft(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Ft(s[2]),end:Ft(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Ft(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=I.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(gt,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||R}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:R,DataLayers:xe,DataFunctions:ls,Layouts:ns,MatchFunctions:Jt,ScaleFunctions:ee,TransformationFunctions:Z,Widgets:jt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),R}},cs=[];hs.use=function(t,...e){if(!cs.includes(t)){if(e.unshift(hs),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}cs.push(t)}};const ds=hs})(),LocusZoom=i.default})(); //# sourceMappingURL=locuszoom.app.min.js.map \ No newline at end of file diff --git a/dist/locuszoom.app.min.js.map b/dist/locuszoom.app.min.js.map index 5e10135c..fb87332e 100644 --- a/dist/locuszoom.app.min.js.map +++ b/dist/locuszoom.app.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/./node_modules/@hapi/hoek/lib/assert.js","webpack://[name]/./node_modules/@hapi/hoek/lib/error.js","webpack://[name]/./node_modules/@hapi/hoek/lib/stringify.js","webpack://[name]/./node_modules/@hapi/topo/lib/index.js","webpack://[name]/./node_modules/just-clone/index.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./node_modules/undercomplicate/esm/lru_cache.js","webpack://[name]/./node_modules/undercomplicate/esm/util.js","webpack://[name]/./node_modules/undercomplicate/esm/requests.js","webpack://[name]/./node_modules/undercomplicate/esm/joins.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./node_modules/undercomplicate/esm/adapter.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/registry/data_ops.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["AssertError","module","exports","condition","args","length","Error","Stringify","super","filter","arg","map","message","join","captureStackTrace","this","assert","JSON","stringify","apply","err","Assert","internals","_items","nodes","options","before","concat","after","group","sort","includes","Array","isArray","node","item","seq","push","manual","valid","_sort","others","other","Object","assign","mergeSort","i","graph","graphAfters","create","groups","expandedGroups","graphNodeItem","ancestors","children","child","visited","sorted","next","j","shouldSeeCount","seenCount","k","seqIndex","value","sortedItem","a","b","getRegExpFlags","regExp","source","flags","global","ignoreCase","multiline","sticky","unicode","clone","obj","result","key","type","toString","call","slice","Date","getTime","RegExp","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","prop","prototype","hasOwnProperty","r","Symbol","toStringTag","RegistryBase","Map","name","has","override","set","delete","from","keys","ClassRegistry","parent_name","source_name","overrides","console","warn","arguments","base","sub","add","LLNode","metadata","prev","LRUCache","max_size","_max_size","_cur_size","_store","_head","_tail","cached","prior","_remove","old","callback","data","getLinkedData","shared_options","entities","dependencies","consolidate","parsed","spec","exec","name_alone","name_deps","deps","split","_parse_declaration","dag","toposort","entries","e","order","responses","provider","depends_on","this_result","Promise","all","then","prior_results","_provider_name","getData","values","all_results","groupBy","records","group_key","item_group","_any_match","left","right","left_key","right_key","right_index","results","left_match_value","right_matches","right_item","left_index","right_match_value","left_match","REGEX_MARKER","parseMarker","test","match","BaseApiAdapter","BaseLZAdapter","config","_config","cache_enabled","cache_size","_enable_cache","_cache","dependent_data","response_text","_buildRequestOptions","cache_key","_getCacheKey","resolve","_performRequest","text","_normalizeResponse","_cache_meta","catch","remove","_annotateRecords","_postProcessResponse","_url","url","_getURL","fetch","response","ok","statusText","parse","params","prefix_namespace","limit_fields","_prefix_namespace","_limit_fields","Set","chr","start","end","superset","find","md","row","reduce","acc","label","a_record","fieldname","suffixer","BaseUMAdapter","_genome_build","genome_build","build","constructor","N","every","fields","record","AssociationLZ","_source_id","request_options","GwasCatalogLZ","_validateBuildSource","source_query","GeneLZ","GeneConstraintLZ","state","genes_data","unique_gene_names","gene","gene_name","query","replace","body","method","headers","LDServer","assoc_data","assoc_variant_name","_findPrefixedKey","assoc_logp_name","refvar","best_hit","ldrefvar","best_logp","variant","log_pvalue","lz_is_ld_refvar","chrom","pos","ref","alt","coord","__find_ld_refvar","_skip_request","ld_refvar","ld_source","ld_population","ld_pop","population","encodeURIComponent","combined","chainRequests","payload","forEach","RecombLZ","StaticSource","_data","PheWASLZ","registry","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","transforms","full_name","field_name","transformations","val","transform","extra","undefined","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","m","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","paths","p","_","__","subject","uniqPaths","arr","elem","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","layout","shared_namespaces","requested_ns","merge","custom_layout","default_layout","property","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","findFields","prefixes","field_finder","all_ns","value_type","a_match","renameField","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","regex","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","DataOperation","join_type","initiator","_callable","_initiator","_params","plot_state","dependent_recordsets","data_layer","sources","_sources","namespace_options","data_operations","namespace_local_names","dependency_order","unshift","ns_pattern","local_name","global_name","dep_spec","requires","namecount","require_name","task","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","parentNode","insert","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","x","width","delay","setTimeout","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","position","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","index","indexOf","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","element","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","_panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","radioId","has_option","choice","defaultName","default_config_display_name","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","addWidget","widget","error","_panel_boundaries","dragging","_interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","line_height","_data_layer_ids_by_z_index","reverse","layer_legend","label_x","label_y","shape_factory","path_y","is_horizontal","color_stops","all_elements","ribbon_group","axis_group","axis_offset","tick_labels","range","scale","domain","axis","tickSize","tickValues","tickFormat","v","to_next_marking","radius","PI","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","_initialized","_layout_idx","_state_id","_data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","_zoom_timeout","_event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","parseFloat","y_axis","z_index","dlid","idx","layout_idx","data_layer_layout","target_layer","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","axes_config","base_x_range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","current_drag","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","namespace","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tick_format","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","panel_boundaries","mouse_guide","Plot","datasource","_remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","_layer_state","_setDefaultState","target_panel","clearPanelData","opts","success_callback","from_layer","onerror","error_callback","base_prefix","layer_target","startsWith","is_valid_layer","some","listener","config_to_sources","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","mutateLayout","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","resize_listener","rescaleSVG","window","addEventListener","trackExternalListener","IntersectionObserver","threshold","observer","entry","intersectionRatio","observe","load_listener","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","_mouse_guide","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","ret","positionStringToInt","suffixre","mult","tokens","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","field_value","numerical_bin","breaks","null_value","curr","categorical_bin","categories","ordinal_cycle","stable_choice","max_cache_size","clear","hash","String","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","effect_direction","input","beta_field","stderr_beta_field","plus_result","neg_result","beta_val","se_val","id_field","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","_tooltips","_global_statuses","_data_contract","_entities","_dependencies","layer_order","current_index","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","fields_unseen","debug","lz_is_match","getDataLayer","getPanel","getPlot","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","tooltip_layout","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","_label_texts","da","dal","_label_lines","dax","abound","bbound","_label_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","_label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","version","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","custom_namespaces","_auto_fields","contents","list","_wrap_join","handle","catalog_data","assoc_key","catalog_key","catalog_logp_name","catalog_by_variant","catalog_flat","claims","best_variant","best","n_catalog_matches","constraint_data","alias","constraint","LocusZoom","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","source_id","Adapters","DataLayers","DataFunctions","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["'use strict';\n\nconst AssertError = require('./error');\n\nconst internals = {};\n\n\nmodule.exports = function (condition, ...args) {\n\n if (condition) {\n return;\n }\n\n if (args.length === 1 &&\n args[0] instanceof Error) {\n\n throw args[0];\n }\n\n throw new AssertError(args);\n};\n","'use strict';\n\nconst Stringify = require('./stringify');\n\n\nconst internals = {};\n\n\nmodule.exports = class extends Error {\n\n constructor(args) {\n\n const msgs = args\n .filter((arg) => arg !== '')\n .map((arg) => {\n\n return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);\n });\n\n super(msgs.join(' ') || 'Unknown error');\n\n if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$\n Error.captureStackTrace(this, exports.assert);\n }\n }\n};\n","'use strict';\n\nconst internals = {};\n\n\nmodule.exports = function (...args) {\n\n try {\n return JSON.stringify.apply(null, args);\n }\n catch (err) {\n return '[Cannot display object: ' + err.message + ']';\n }\n};\n","'use strict';\n\nconst Assert = require('@hapi/hoek/lib/assert');\n\n\nconst internals = {};\n\n\nexports.Sorter = class {\n\n constructor() {\n\n this._items = [];\n this.nodes = [];\n }\n\n add(nodes, options) {\n\n options = options || {};\n\n // Validate rules\n\n const before = [].concat(options.before || []);\n const after = [].concat(options.after || []);\n const group = options.group || '?';\n const sort = options.sort || 0; // Used for merging only\n\n Assert(!before.includes(group), `Item cannot come before itself: ${group}`);\n Assert(!before.includes('?'), 'Item cannot come before unassociated items');\n Assert(!after.includes(group), `Item cannot come after itself: ${group}`);\n Assert(!after.includes('?'), 'Item cannot come after unassociated items');\n\n if (!Array.isArray(nodes)) {\n nodes = [nodes];\n }\n\n for (const node of nodes) {\n const item = {\n seq: this._items.length,\n sort,\n before,\n after,\n group,\n node\n };\n\n this._items.push(item);\n }\n\n // Insert event\n\n if (!options.manual) {\n const valid = this._sort();\n Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');\n }\n\n return this.nodes;\n }\n\n merge(others) {\n\n if (!Array.isArray(others)) {\n others = [others];\n }\n\n for (const other of others) {\n if (other) {\n for (const item of other._items) {\n this._items.push(Object.assign({}, item)); // Shallow cloned\n }\n }\n }\n\n // Sort items\n\n this._items.sort(internals.mergeSort);\n for (let i = 0; i < this._items.length; ++i) {\n this._items[i].seq = i;\n }\n\n const valid = this._sort();\n Assert(valid, 'merge created a dependencies error');\n\n return this.nodes;\n }\n\n sort() {\n\n const valid = this._sort();\n Assert(valid, 'sort created a dependencies error');\n\n return this.nodes;\n }\n\n _sort() {\n\n // Construct graph\n\n const graph = {};\n const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives\n const groups = Object.create(null);\n\n for (const item of this._items) {\n const seq = item.seq; // Unique across all items\n const group = item.group;\n\n // Determine Groups\n\n groups[group] = groups[group] || [];\n groups[group].push(seq);\n\n // Build intermediary graph using 'before'\n\n graph[seq] = item.before;\n\n // Build second intermediary graph with 'after'\n\n for (const after of item.after) {\n graphAfters[after] = graphAfters[after] || [];\n graphAfters[after].push(seq);\n }\n }\n\n // Expand intermediary graph\n\n for (const node in graph) {\n const expandedGroups = [];\n\n for (const graphNodeItem in graph[node]) {\n const group = graph[node][graphNodeItem];\n groups[group] = groups[group] || [];\n expandedGroups.push(...groups[group]);\n }\n\n graph[node] = expandedGroups;\n }\n\n // Merge intermediary graph using graphAfters into final graph\n\n for (const group in graphAfters) {\n if (groups[group]) {\n for (const node of groups[group]) {\n graph[node].push(...graphAfters[group]);\n }\n }\n }\n\n // Compile ancestors\n\n const ancestors = {};\n for (const node in graph) {\n const children = graph[node];\n for (const child of children) {\n ancestors[child] = ancestors[child] || [];\n ancestors[child].push(node);\n }\n }\n\n // Topo sort\n\n const visited = {};\n const sorted = [];\n\n for (let i = 0; i < this._items.length; ++i) { // Looping through item.seq values out of order\n let next = i;\n\n if (ancestors[i]) {\n next = null;\n for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values\n if (visited[j] === true) {\n continue;\n }\n\n if (!ancestors[j]) {\n ancestors[j] = [];\n }\n\n const shouldSeeCount = ancestors[j].length;\n let seenCount = 0;\n for (let k = 0; k < shouldSeeCount; ++k) {\n if (visited[ancestors[j][k]]) {\n ++seenCount;\n }\n }\n\n if (seenCount === shouldSeeCount) {\n next = j;\n break;\n }\n }\n }\n\n if (next !== null) {\n visited[next] = true;\n sorted.push(next);\n }\n }\n\n if (sorted.length !== this._items.length) {\n return false;\n }\n\n const seqIndex = {};\n for (const item of this._items) {\n seqIndex[item.seq] = item;\n }\n\n this._items = [];\n this.nodes = [];\n\n for (const value of sorted) {\n const sortedItem = seqIndex[value];\n this.nodes.push(sortedItem.node);\n this._items.push(sortedItem);\n }\n\n return true;\n }\n};\n\n\ninternals.mergeSort = (a, b) => {\n\n return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);\n};\n","module.exports = clone;\n\n/*\n Deep clones all properties except functions\n\n var arr = [1, 2, 3];\n var subObj = {aa: 1};\n var obj = {a: 3, b: 5, c: arr, d: subObj};\n var objClone = clone(obj);\n arr.push(4);\n subObj.bb = 2;\n obj; // {a: 3, b: 5, c: [1, 2, 3, 4], d: {aa: 1}}\n objClone; // {a: 3, b: 5, c: [1, 2, 3], d: {aa: 1, bb: 2}}\n*/\n\nfunction clone(obj) {\n if (typeof obj == 'function') {\n return obj;\n }\n var result = Array.isArray(obj) ? [] : {};\n for (var key in obj) {\n // include prototype properties\n var value = obj[key];\n var type = {}.toString.call(value).slice(8, -1);\n if (type == 'Array' || type == 'Object') {\n result[key] = clone(value);\n } else if (type == 'Date') {\n result[key] = new Date(value.getTime());\n } else if (type == 'RegExp') {\n result[key] = RegExp(value.source, getRegExpFlags(value));\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction getRegExpFlags(regExp) {\n if (typeof regExp.source.flags == 'string') {\n return regExp.source.flags;\n } else {\n var flags = [];\n regExp.global && flags.push('g');\n regExp.ignoreCase && flags.push('i');\n regExp.multiline && flags.push('m');\n regExp.sticky && flags.push('y');\n regExp.unicode && flags.push('u');\n return flags.join('');\n }\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.14.0-beta.2';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","// Implement an LRU Cache\n\nclass LLNode {\n constructor(key, value, metadata = {}, prev = null, next = null) {\n this.key = key;\n this.value = value;\n this.metadata = metadata;\n this.prev = prev;\n this.next = next;\n }\n}\n\nclass LRUCache {\n constructor(max_size = 3) {\n this._max_size = max_size;\n this._cur_size = 0; // replace with map.size so we aren't managing manually?\n this._store = new Map();\n\n // Track LRU state\n this._head = null;\n this._tail = null;\n\n // Validate options\n if (max_size === null || max_size < 0) {\n throw new Error('Cache \"max_size\" must be >= 0');\n }\n }\n\n has(key) {\n // Check key membership without updating LRU\n return this._store.has(key);\n }\n\n get(key) {\n // Retrieve value from cache (if present) and update LRU cache accordingly\n const cached = this._store.get(key);\n if (!cached) {\n return null;\n }\n if (this._head !== cached) {\n // Rewrite the cached value to ensure it is head of the list\n this.add(key, cached.value);\n }\n return cached.value;\n }\n\n add(key, value, metadata = {}) {\n // Add an item. Forcibly replaces the existing cached value for the same key.\n if (this._max_size === 0) {\n // Don't cache items if cache has 0 size. Also prevent users from trying \"negative number for infinite cache\".\n return;\n }\n\n const prior = this._store.get(key);\n if (prior) {\n this._remove(prior);\n }\n\n const node = new LLNode(key, value, metadata, null, this._head);\n\n if (this._head) {\n this._head.prev = node;\n } else {\n this._tail = node;\n }\n\n this._head = node;\n this._store.set(key, node);\n\n if (this._max_size >= 0 && this._cur_size >= this._max_size) {\n const old = this._tail;\n this._tail = this._tail.prev;\n this._remove(old);\n }\n this._cur_size += 1;\n }\n\n\n // Cache manipulation methods\n clear() {\n this._head = null;\n this._tail = null;\n this._cur_size = 0;\n this._store = new Map();\n }\n\n // Public method, remove by key\n remove(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return false;\n }\n this._remove(cached);\n return true;\n }\n\n // Internal implementation, useful when working on list\n _remove(node) {\n if (node.prev !== null) {\n node.prev.next = node.next;\n } else {\n this._head = node.next;\n }\n\n if (node.next !== null) {\n node.next.prev = node.prev;\n } else {\n this._tail = node.prev;\n }\n this._store.delete(node.key);\n this._cur_size -= 1;\n }\n\n /**\n * Find a matching item in the cache, or return null. This is useful for \"approximate match\" semantics,\n * to check if something qualifies as a cache hit despite not having the exact same key.\n * (Example: zooming into a region, where the smaller region is a subset of something already cached)\n * @param callback\n * @returns {null|LLNode}\n */\n find(callback) {\n let node = this._head;\n while (node) {\n const next = node.next;\n if (callback(node)) {\n return node;\n }\n node = next;\n }\n }\n}\n\nexport { LRUCache };\n","import justclone from 'just-clone';\n\n/**\n * The \"just-clone\" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.\n * @param data\n * @returns {*}\n */\nfunction clone(data) {\n if (typeof data !== 'object') {\n return data;\n }\n return justclone(data);\n}\n\nexport { clone };\n","/**\n * Perform a series of requests, respecting order of operations\n */\n\nimport {Sorter} from '@hapi/topo';\n\n\nfunction _parse_declaration(spec) {\n // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.\n const parsed = /^(?\\w+)$|((?\\w+)+\\(\\s*(?[^)]+?)\\s*\\))/.exec(spec);\n if (!parsed) {\n throw new Error(`Unable to parse dependency specification: ${spec}`);\n }\n\n let {name_alone, name_deps, deps} = parsed.groups;\n if (name_alone) {\n return [name_alone, []];\n }\n\n deps = deps.split(/\\s*,\\s*/);\n return [name_deps, deps];\n}\n\nfunction getLinkedData(shared_options, entities, dependencies, consolidate = true) {\n if (!dependencies.length) {\n return [];\n }\n\n const parsed = dependencies.map((spec) => _parse_declaration(spec));\n const dag = new Map(parsed);\n\n // Define the order to perform requests in, based on a DAG\n const toposort = new Sorter();\n for (let [name, deps] of dag.entries()) {\n try {\n toposort.add(name, {after: deps, group: name});\n } catch (e) {\n throw new Error(`Invalid or possible circular dependency specification for: ${name}`);\n }\n }\n const order = toposort.nodes;\n\n // Verify that all requested entities exist by name!\n const responses = new Map();\n for (let name of order) {\n const provider = entities.get(name);\n if (!provider) {\n throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);\n }\n\n // Each promise should only be triggered when the things it depends on have been resolved\n const depends_on = dag.get(name) || [];\n const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));\n\n const this_result = prereq_promises.then((prior_results) => {\n // Each request will be told the name of the provider that requested it. This can be used during post-processing,\n // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)\n // This has a secondary effect: it ensures that any changes made to \"shared\" options in one adapter will\n // not leak out to others via a mutable shared object reference.\n const options = Object.assign({_provider_name: name}, shared_options);\n return provider.getData(options, ...prior_results);\n });\n responses.set(name, this_result);\n }\n return Promise.all([...responses.values()])\n .then((all_results) => {\n if (consolidate) {\n // Some usages- eg fetch + data join tasks- will only require the last response in the sequence\n // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)\n return all_results[all_results.length - 1];\n }\n return all_results;\n });\n}\n\n\nexport {getLinkedData};\n\n// For testing only\nexport {_parse_declaration};\n","/**\n * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.\n */\nimport { clone } from './util';\n\n\nfunction groupBy(records, group_key) {\n const result = new Map();\n for (let item of records) {\n const item_group = item[group_key];\n\n if (typeof item_group === 'undefined') {\n throw new Error(`All records must specify a value for the field \"${group_key}\"`);\n }\n if (typeof item_group === 'object') {\n // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)\n throw new Error('Attempted to group on a field with non-primitive values');\n }\n\n let group = result.get(item_group);\n if (!group) {\n group = [];\n result.set(item_group, group);\n }\n group.push(item);\n }\n return result;\n}\n\n\nfunction _any_match(type, left, right, left_key, right_key) {\n // Helper that consolidates logic for all three join types\n const right_index = groupBy(right, right_key);\n const results = [];\n for (let item of left) {\n const left_match_value = item[left_key];\n const right_matches = right_index.get(left_match_value) || [];\n if (right_matches.length) {\n // Record appears on both left and right; equiv to an inner join\n results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));\n } else if (type !== 'inner') {\n // Record appears on left but not right\n results.push(clone(item));\n }\n }\n\n if (type === 'outer') {\n // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side\n const left_index = groupBy(left, left_key);\n for (let item of right) {\n const right_match_value = item[right_key];\n const left_matches = left_index.get(right_match_value) || [];\n if (!left_matches.length) {\n results.push(clone(item));\n }\n }\n }\n return results;\n}\n\n/**\n * Equivalent to LEFT OUTER JOIN in SQL.\n * @param left\n * @param right\n * @param left_key\n * @param right_key\n * @returns {*[]}\n */\nfunction left_match(left, right, left_key, right_key) {\n return _any_match('left', ...arguments);\n}\n\nfunction inner_match(left, right, left_key, right_key) {\n return _any_match('inner', ...arguments);\n}\n\nfunction full_outer_match(left, right, left_key, right_key) {\n return _any_match('outer', ...arguments);\n}\n\nexport {left_match, inner_match, full_outer_match, groupBy};\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 1 }]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 2 }]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nimport {BaseUrlAdapter} from 'undercomplicate';\n\nimport {parseMarker} from '../helpers/parse';\n\n// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs.\n// Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal\n// methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the\n// private API methods exist in the base class.\n\n/**\n * Replaced with the BaseLZAdapter class.\n * @public\n * @deprecated\n */\nclass BaseAdapter {\n constructor() {\n throw new Error('The \"BaseAdapter\" and \"BaseApiAdapter\" classes have been replaced in LocusZoom 0.14. See migration guide for details.');\n }\n}\n\n/**\n * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @deprecated\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {}\n\n\n/**\n * @param {object} config\n * @param [config.cache_enabled=true]\n * @param [config.cache_size=3]\n * @param [config.url]\n * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name.\n * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant)\n * Typically, this is only disabled if the response payload is very unusual\n * @param {String[]} [limit_fields=null] If an API returns far more data than is needed, this can be used to simplify\n * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.\n */\nclass BaseLZAdapter extends BaseUrlAdapter {\n constructor(config = {}) {\n if (config.params) {\n // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places.\n console.warn('Deprecation warning: all options in \"config.params\" should now be specified as top level keys.');\n Object.assign(config, config.params || {});\n delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality!\n }\n super(config);\n\n // Prefix the namespace for this source to all fieldnames: id -> assoc.id\n // This is useful for almost all layers because the layout object says where to find every field, exactly.\n // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on\n // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear\n // in the response. (gene_name instead of genes.gene_name)\n const { prefix_namespace = true, limit_fields } = config;\n this._prefix_namespace = prefix_namespace;\n this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields\n }\n\n /**\n * Determine how a particular request will be identified in cache. Most LZ requests are region based,\n * so the default is a string concatenation of `chr_start_end`\n * @param options Receives plot.state plus any other request options defined by this source\n * @returns {string}\n * @private\n */\n _getCacheKey(options) {\n // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default\n let {chr, start, end} = options; // Current view: plot.state\n\n // Does a prior cache hit qualify as a superset of the ROI?\n const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end);\n if (superset) {\n ({ chr, start, end } = superset.metadata);\n }\n\n // The default cache key is region-based, and this method only returns the region-based part of the cache hit\n // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check\n options._cache_meta = { chr, start, end };\n return `${chr}_${start}_${end}`;\n }\n\n /**\n * Add the \"local namespace\" as a prefix for every field returned for this request. Eg if the association api\n * returns a field called variant, and the source is referred to as \"assoc\" within a particular data layer, then\n * the returned records will have a field called \"assoc:variant\"\n *\n * @param records\n * @param options\n * @returns {*}\n * @private\n */\n _postProcessResponse(records, options) {\n if (!this._prefix_namespace || !Array.isArray(records)) {\n return records;\n }\n\n // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for\n // assoc data might see \"variant\" as \"assoc.variant\"\n const { _limit_fields } = this;\n const { _provider_name } = options;\n\n return records.map((row) => {\n return Object.entries(row).reduce(\n (acc, [label, value]) => {\n // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload.\n if (!_limit_fields || _limit_fields.has(label)) {\n acc[`${_provider_name}:${label}`] = value;\n }\n return acc;\n },\n {}\n );\n });\n }\n\n /**\n * Convenience method, manually called in LZ sources that deal with dependent data.\n *\n * In the last step of fetching data, LZ adds a prefix to each field name.\n * This means that operations like \"build query based on prior data\" can't just ask for \"log_pvalue\" because\n * they are receiving \"assoc:log_pvalue\" or some such unknown prefix.\n *\n * This helper lets us use dependent data more easily. Not every adapter needs to use this method.\n *\n * @param {Object} a_record One record (often the first one in a set of records)\n * @param {String} fieldname The desired fieldname, eg \"log_pvalue\"\n */\n _findPrefixedKey(a_record, fieldname) {\n const suffixer = new RegExp(`:${fieldname}$`);\n const match = Object.keys(a_record).find((key) => suffixer.test(key));\n if (!match) {\n throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`);\n }\n return match;\n }\n}\n\n\n/**\n * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies\n * of one particular web server.\n */\nclass BaseUMAdapter extends BaseLZAdapter {\n /**\n * @param {Object} config\n * @param {String} [config.build] The genome build to be used by all requests for this adapter.\n */\n constructor(config = {}) {\n super(config);\n // The UM portaldev API accepts an (optional) parameter \"genome_build\"\n this._genome_build = config.genome_build || config.build;\n }\n\n _validateBuildSource(build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${this.constructor.name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`);\n }\n }\n\n // Special behavior for the UM portaldev API: col -> row format normalization\n /**\n * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.\n * @param response_text\n * @param options\n * @returns {Object[]}\n * @private\n */\n _normalizeResponse(response_text, options) {\n let data = super._normalizeResponse(...arguments);\n // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload\n data = data.data || data;\n\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the columns, and create an object for each row record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n}\n\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n *\n * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL\n */\nclass AssociationLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files\n const { source } = config;\n this._source_id = source;\n }\n\n _getURL (request_options) {\n const {chr, start, end} = request_options;\n const base = super._getURL(request_options);\n return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`;\n }\n}\n\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GwasCatalogLZ extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build] The genome build to use when requesting the specific genomic region.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n const source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given.\n // We will avoid transforming or modifying the payload.\n this._prefix_namespace = false;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GeneConstraintLZ extends BaseLZAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config = {}) {\n super(config);\n this._prefix_namespace = false;\n }\n\n _buildRequestOptions(state, genes_data) {\n const build = state.genome_build || this._config.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = new Set();\n for (let gene of genes_data) {\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n unique_gene_names.add(gene.gene_name);\n }\n\n state.query = [...unique_gene_names.values()].map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n state.build = build;\n return Object.assign({}, state);\n }\n\n _performRequest(options) {\n let {query, build} = options;\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears to set max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve([]);\n }\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n\n const url = this._getURL(options);\n\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.\n */\n _normalizeResponse(response_text) {\n if (typeof response_text !== 'string') {\n // If the query short-circuits, we receive an empty list instead of a string\n return response_text;\n }\n const data = JSON.parse(response_text);\n return data.data;\n }\n}\n\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant and yse that as the LD reference variant.\n *\n * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly\n * if this information is not provided.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass LDServer extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.method='rsquare'] The metric used to calculate LD\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n __find_ld_refvar(state, assoc_data) {\n const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant');\n const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue');\n\n // Determine the reference variant (via user selected OR automatic-per-track)\n let refvar;\n let best_hit = {};\n if (state.ldrefvar) {\n // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data\n refvar = state.ldrefvar;\n best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {};\n } else {\n // find highest log-value and associated var spec\n let best_logp = 0;\n for (let item of assoc_data) {\n const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item;\n if (log_pvalue > best_logp) {\n best_logp = log_pvalue;\n refvar = variant;\n best_hit = item;\n }\n }\n }\n\n // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting.\n // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase.\n best_hit.lz_is_ld_refvar = true;\n\n // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server,\n // the variant fields must be normalized to a specific format. All later LD operations will use that format.\n const match = parseMarker(refvar, true);\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n\n const [chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip?\n if (ref && alt) {\n refvar += `_${ref}/${alt}`;\n }\n\n const coord = +pos;\n // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably\n // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby.\n if ((coord && state.ldrefvar && state.chr) && (chrom !== state.chr || coord < state.start || coord > state.end)) {\n // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a\n // *copy* of plot.state, so wiping here doesn't remove the original value.\n state.ldrefvar = null;\n return this.__find_ld_refvar(state, assoc_data);\n }\n\n // Return the reference variant, in a normalized format suitable for LDServer queries\n return refvar;\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n // No variants, so no need to annotate association data with LD!\n // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region)\n base._skip_request = true;\n return base;\n }\n\n base.ld_refvar = this.__find_ld_refvar(state, assoc_data);\n\n // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config\n const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let ld_source = state.ld_source || this._config.source || '1000G';\n const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL\n\n if (ld_source === '1000G' && genome_build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n ld_source = '1000G-FRZ09';\n }\n\n this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option\n return Object.assign({}, base, { genome_build, ld_source, ld_population });\n }\n\n _getURL(request_options) {\n const method = this._config.method || 'rsquare';\n const {\n chr, start, end,\n ld_refvar,\n genome_build, ld_source, ld_population,\n } = request_options;\n\n const base = super._getURL(request_options);\n\n return [\n base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(ld_refvar),\n '&chrom=', encodeURIComponent(chr),\n '&start=', encodeURIComponent(start),\n '&stop=', encodeURIComponent(end),\n ].join('');\n }\n\n _getCacheKey(options) {\n // LD is keyed by more than just region; append other parameters to the base cache key\n const base = super._getCacheKey(options);\n const { ld_refvar, ld_source, ld_population } = options;\n return `${base}_${ld_refvar}_${ld_source}_${ld_population}`;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird.\n return Promise.resolve([]);\n }\n\n const url = this._getURL(options);\n\n // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseUMAdapter {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['position', 'recomb_rate'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {object} config.data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseLZAdapter {\n constructor(config = {}) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n super(...arguments);\n const { data } = config;\n if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key\n throw new Error(\"'StaticSource' must provide data as required option 'config.data'\");\n }\n this._data = data;\n }\n\n _performRequest(options) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will\n * be used to provide PheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const base = super._getURL(request_options);\n const url = [\n base,\n \"?filter=variant eq '\", encodeURIComponent(request_options.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n _getCacheKey(options) {\n // Not a region based source; don't do anything smart for cache check\n return this._getURL(options);\n }\n}\n\n// Deprecated symbols\nexport { BaseAdapter, BaseApiAdapter };\n\n// Usually used as a parent class for custom code\nexport { BaseLZAdapter, BaseUMAdapter };\n\n// Usually used as a standalone class\nexport {\n AssociationLZ,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","import {LRUCache} from './lru_cache';\nimport {clone} from './util';\n\nclass BaseAdapter {\n constructor(config = {}) {\n this._config = config;\n const {\n // Cache control\n cache_enabled = true,\n cache_size = 3,\n } = config;\n this._enable_cache = cache_enabled;\n this._cache = new LRUCache(cache_size);\n }\n\n _buildRequestOptions(options, dependent_data) {\n // Perform any pre-processing required that may influence the request. Receives an array with the payloads\n // for each request that preceded this one in the dependency chain\n // This method may optionally take dependent data into account\n return Object.assign({}, options);\n }\n\n _getCacheKey(options) {\n /* istanbul ignore next */\n if (this._enable_cache) {\n throw new Error('Method not implemented');\n }\n return null;\n }\n\n /**\n * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)\n * @param options\n * @returns {Promise}\n * @private\n */\n _performRequest(options) {\n /* istanbul ignore next */\n throw new Error('Not implemented');\n }\n\n _normalizeResponse(response_text, options) {\n // Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.\n return response_text;\n }\n\n /**\n * Perform custom client-side operations on the retrieved data. For example, add calculated fields or\n * perform rapid client-side filtering on cached data\n * @param records\n * @param {Object} options\n * @returns {*}\n * @private\n */\n _annotateRecords(records, options) {\n return records;\n }\n\n /**\n * A hook to transform the response after all operations are done. For example, this can be used to prefix fields\n * with a namespace unique to the request, like assoc.log_pvalue. (that way, annotations and validation can happen\n * on the actual API payload, without having to guess what the fields were renamed to).\n * @param records\n * @param options\n * @private\n */\n _postProcessResponse(records, options) {\n return records;\n }\n\n getData(options = {}, ...dependent_data) {\n // Public facing method to define, perform, and process the request\n options = this._buildRequestOptions(options, ...dependent_data);\n\n // Then retrieval and parse steps: parse + normalize response, annotate\n const cache_key = this._getCacheKey(options);\n\n let result;\n if (this._enable_cache && this._cache.has(cache_key)) {\n result = this._cache.get(cache_key);\n } else {\n // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)\n // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry\n // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,\n // even if the actual cache key wasn't an exact match\n result = Promise.resolve(this._performRequest(options))\n // Note: we cache the normalized (parsed) response\n .then((text) => this._normalizeResponse(text, options));\n this._cache.add(cache_key, result, options._cache_meta);\n // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request\n // Otherwise, temporary failures couldn't be resolved by trying again in a moment\n result.catch((e) => this._cache.remove(cache_key));\n }\n\n return result\n // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache\n .then((data) => clone(data))\n .then((records) => this._annotateRecords(records, options))\n .then((records) => this._postProcessResponse(records, options));\n }\n}\n\n\n/**\n * Fetch data over the web\n */\nclass BaseUrlAdapter extends BaseAdapter {\n constructor(config = {}) {\n super(config);\n this._url = config.url;\n }\n\n\n // Default cache key is the URL for the request.\n _getCacheKey(options) {\n return this._getURL(options);\n }\n\n _getURL(options) {\n return this._url;\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.\n // (not validating in constructor allows URL adapter to be used as more generic parent class)\n if (!this._url) {\n throw new Error('Web based resources must specify a resource URL as option \"url\"');\n }\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n // In most cases, we store the response as text so that the copy in cache is clean (no mutable references)\n return response.text();\n });\n }\n\n _normalizeResponse(response_text, options) {\n if (typeof response_text === 'string') {\n return JSON.parse(response_text);\n }\n // Some custom usages will return an object directly; return a copy of the object\n return response_text;\n }\n}\n\nexport { BaseAdapter, BaseUrlAdapter };\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import TRANSFORMS from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n // Two scenarios: we are requesting a field by full name, OR there are transforms to apply\n // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`\n const field_pattern = /^(?:\\w+:\\w+|^\\w+)(?:\\|\\w+)*$/;\n if (!field_pattern.test(field)) {\n throw new Error(`Invalid field specifier: '${field}'`);\n }\n\n const [name, ...transforms] = field.split('|');\n\n this.full_name = field; // fieldname + transforms\n this.field_name = name; // just fieldname\n this.transformations = transforms.map((name) => TRANSFORMS.get(name));\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (data[this.field_name] !== undefined) { // Fallback: value sans transforms\n val = data[this.field_name];\n } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations\n val = extra[this.field_name];\n } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {}\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * @module\n * @private\n */\nimport {getLinkedData} from 'undercomplicate';\n\nimport { DATA_OPS } from '../registry';\n\n\nclass DataOperation {\n /**\n * Perform a data operation (such as a join)\n * @param {String} join_type\n * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!\n * @param params Optional user/layout parameters to be passed to the data function\n */\n constructor(join_type, initiator, params) {\n this._callable = DATA_OPS.get(join_type);\n this._initiator = initiator;\n this._params = params || [];\n }\n\n getData(plot_state, ...dependent_recordsets) {\n // Most operations are joins: they receive two pieces of data (eg left + right)\n // Other ops are possible, like consolidating just one set of records to best value per key\n // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]\n\n // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options\n const context = {plot_state, data_layer: this._initiator};\n return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));\n }\n}\n\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a\n * designated order.\n *\n * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can\n * perform its own calculations, filter results, and apply transforms without influencing other layers.\n * (while still respecting a shared cache where appropriate)\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n /**\n * Parse the data layer configuration when a layer is first created.\n * Validate config, and return entities and dependencies in a format usable for data retrieval.\n * This is used by data layers, and also other data-retrieval functions (like subscribeToDate).\n *\n * Inherent assumptions:\n * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed.\n * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be\n * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the\n * removed adapter.\n * @param {Object} namespace_options\n * @param {Array} data_operations\n * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations,\n * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate\n * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care!\n * @returns {Array} Map of entities and list of dependencies\n */\n config_to_sources(namespace_options = {}, data_operations = [], initiator) {\n const entities = new Map();\n const namespace_local_names = Object.keys(namespace_options);\n\n // 1. Specify how to coordinate data. Precedence:\n // a) EXPLICIT fetch logic,\n // b) IMPLICIT auto-generate fetch order if there is only one NS,\n // c) Throw \"spec required\" error if > 1, because 2 adapters may need to be fetched in a sequence\n let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from}\n if (!dependency_order) {\n dependency_order = { type: 'fetch', from: namespace_local_names };\n data_operations.unshift(dependency_order);\n }\n\n // Validate that all NS items are available to the root requester in DataSources. All layers recognize a\n // default value, eg people copying the examples tend to have defined a datasource called \"assoc\"\n const ns_pattern = /^\\w+$/;\n for (let [local_name, global_name] of Object.entries(namespace_options)) {\n if (!ns_pattern.test(local_name)) {\n throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`);\n }\n\n const source = this._sources.get(global_name);\n if (!source) {\n throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`);\n }\n entities.set(local_name, source);\n\n // Note: Dependency spec checker will consider \"ld(assoc)\" to match a namespace called \"ld\"\n if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) {\n // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join.\n // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch\n // Thus the default behavior is \"fetch all namespaces as though they don't depend on anything.\n // If they depend on something, only then does \"data_ops[@type=fetch].from\" need to be mutated\n dependency_order.from.push(local_name);\n }\n }\n\n let dependencies = Array.from(dependency_order.from);\n\n // Now check all joins. Are namespaces valid? Are they requesting known data?\n for (let config of data_operations) {\n let {type, name, requires, params} = config;\n if (type !== 'fetch') {\n let namecount = 0;\n if (!name) {\n name = config.name = `join${namecount}`;\n namecount += 1;\n }\n\n if (entities.has(name)) {\n throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`);\n }\n requires.forEach((require_name) => {\n if (!entities.has(require_name)) {\n throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`);\n }\n });\n\n const task = new DataOperation(type, initiator, params);\n entities.set(name, task);\n dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB)\n }\n }\n return [entities, dependencies];\n }\n\n /**\n * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end)\n * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts.\n * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances\n * (things that implement a method getData).\n * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order\n * @returns {Promise}\n */\n getData(plot_state, entities, dependencies) {\n if (!dependencies.length) {\n return Promise.resolve([]);\n }\n // The last dependency (usually the last join operation) determines the last thing returned.\n return getLinkedData(plot_state, entities, dependencies, true);\n }\n}\n\n\nexport default Requester;\n\nexport {DataOperation as _JoinTask};\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import WIDGETS from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n const options = this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n this.addWidget(layout);\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown)\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Add a new widget to the toolbar.\n * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to:\n * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here.\n * - call widget.show() to ensure that the widget is initialized and rendered correctly\n * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default.\n * @param {Object} layout The layout object describing the desired widget\n * @returns {layout.type}\n */\n addWidget(layout) {\n try {\n const widget = WIDGETS.create(layout.type, layout, this);\n this.widgets.push(widget);\n return widget;\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 14,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n const layer_legend = this.parent.data_layers[id].layout.legend;\n if (Array.isArray(layer_legend)) {\n layer_legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape === 'ribbon') {\n // Color ribbons describe a series of color stops: small boxes of color across a continuous\n // scale. Drawn horizontally, or vertically, like:\n // [red | orange | yellow | green ] label\n // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way.\n const width = +element.width || 25;\n const height = +element.height || width;\n const is_horizontal = (element.orientation || 'vertical') === 'horizontal';\n let color_stops = element.color_stops;\n\n const all_elements = selector.append('g');\n const ribbon_group = all_elements.append('g');\n const axis_group = all_elements.append('g');\n let axis_offset = 0;\n if (element.tick_labels) {\n let range;\n if (is_horizontal) {\n range = [0, width * color_stops.length - 1]; // 1 px offset to align tick with inner borders\n } else {\n range = [height * color_stops.length - 1, 0];\n }\n const scale = d3.scaleLinear()\n .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode\n .range(range);\n const axis = (is_horizontal ? d3.axisTop : d3.axisRight)(scale)\n .tickSize(3)\n .tickValues(element.tick_labels)\n .tickFormat((v) => v);\n axis_group\n .call(axis)\n .attr('class', 'lz-axis');\n let bcr = axis_group.node().getBoundingClientRect();\n axis_offset = bcr.height;\n }\n if (is_horizontal) {\n // Shift axis down (so that tick marks aren't above the origin)\n axis_group\n .attr('transform', `translate(0, ${axis_offset})`);\n // Ribbon appears below axis\n ribbon_group\n .attr('transform', `translate(0, ${axis_offset})`);\n } else {\n // Vertical mode: Shift axis ticks to the right of the ribbon\n all_elements.attr('transform', 'translate(5, 0)');\n axis_group\n .attr('transform', `translate(${width}, 0)`);\n }\n\n if (!is_horizontal) {\n // Vertical mode: renders top -> bottom but scale is usually specified low..high\n color_stops = color_stops.slice();\n color_stops.reverse();\n }\n for (let i = 0; i < color_stops.length; i++) {\n const color = color_stops[i];\n const to_next_marking = is_horizontal ? `translate(${width * i}, 0)` : `translate(0, ${height * i})`;\n ribbon_group\n .append('rect')\n .attr('class', element.class || '')\n .attr('stroke', 'black')\n .attr('transform', to_next_marking)\n .attr('stroke-width', 0.5)\n .attr('width', width)\n .attr('height', height)\n .attr('fill', color)\n .call(applyStyles, element.style || {});\n }\n\n // Note: In vertical mode, it's usually easier to put the label above the legend as a separate marker\n // This is because the legend element label is drawn last (can't use it's size to position the ribbon, which is drawn first)\n if (!is_horizontal && element.label) {\n throw new Error('Legend labels not supported for vertical ribbons (use a separate legend item as text instead)');\n }\n // This only makes sense for horizontal labels.\n label_x = (width * color_stops.length + padding);\n label_y += axis_offset;\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset] The distance between the axis title and the axis. Use this to prevent\n * the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly.\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n if (typeof layout.id !== 'string' || !layout.id) {\n throw new Error('Panel layouts must specify \"id\"');\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this._layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this._state_id = this.id;\n this.state[this._state_id] = this.state[this._state_id] || {};\n } else {\n this.state = null;\n this._state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this._data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this._data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this._zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this._event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this._event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this._data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this._data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id]._layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n const target_layer = this.data_layers[id];\n if (!target_layer) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n target_layer.destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (target_layer.svg.container) {\n target_layer.svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(target_layer._layout_idx, 1);\n delete this.state[target_layer._state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id]._layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n const { cliparea } = this.layout;\n\n // Set and position the inner border, style if necessary\n const { margin } = this.layout;\n this.inner_border\n .attr('x', margin.left)\n .attr('y', margin.top)\n .attr('width', this.parent_plot.layout.width - (margin.left + margin.right))\n .attr('height', this.layout.height - (margin.top + margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n const axes_config = this.layout.axes;\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (axes_config.x.range) {\n base_x_range.start = axes_config.x.range.start || base_x_range.start;\n base_x_range.end = axes_config.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: cliparea.height, end: 0 };\n if (axes_config.y1.range) {\n base_y1_range.start = axes_config.y1.range.start || base_y1_range.start;\n base_y1_range.end = axes_config.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: cliparea.height, end: 0 };\n if (axes_config.y2.range) {\n base_y2_range.start = axes_config.y2.range.start || base_y2_range.start;\n base_y2_range.end = axes_config.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n let { _interaction } = this.parent;\n const current_drag = _interaction.dragging;\n if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (_interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = _interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = _interaction.zooming.center - margin.left - this.layout.origin.x;\n const offset_ratio = anchor / cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (current_drag) {\n switch (current_drag.method) {\n case 'background':\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n } else {\n anchor = current_drag.start_x - margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${current_drag.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y;\n ranges[y_shifted][1] = +current_drag.dragged_y;\n } else {\n anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3);\n ranges[y_shifted][0] = cliparea.height;\n ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent._interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n // Redefine b/c might have been changed during call to parent re-render\n _interaction = this.parent._interaction;\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this._zoom_timeout !== null) {\n clearTimeout(this._zoom_timeout);\n }\n this._zoom_timeout = setTimeout(() => {\n this.parent._interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this._initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((id) => {\n const axis = this.layout.axes[id];\n if (!Object.keys(axis).length || axis.render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n axis.render = false;\n } else {\n axis.render = true;\n axis.label = axis.label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n const layout = this.layout;\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n layout.height = Math.max(Math.round(+height), layout.min_height);\n }\n }\n layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0);\n layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', layout.height);\n }\n if (this._initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n const { cliparea, margin } = this.layout;\n if (!isNaN(top) && top >= 0) {\n margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (margin.top + margin.bottom > this.layout.height) {\n extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2);\n margin.top -= extra;\n margin.bottom -= extra;\n }\n if (margin.left + margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2);\n margin.left -= extra;\n margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n margin[m] = Math.max(margin[m], 0);\n });\n cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0);\n cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0);\n cliparea.origin.x = margin.left;\n cliparea.origin.y = margin.top;\n\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this._data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent._panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n const { parent } = this;\n const y_index = this.layout.y_index;\n if (parent._panel_ids_by_y_index[y_index - 1]) {\n parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1];\n parent._panel_ids_by_y_index[y_index - 1] = this.id;\n parent.applyPanelYIndexesToPanelLayouts();\n parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n const { _panel_ids_by_y_index } = this.parent;\n if (_panel_ids_by_y_index[this.layout.y_index + 1]) {\n _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1];\n _panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this._data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this._data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this._data_promises)\n .then(() => {\n this._initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([\\w+_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future.\n * @event data_from_layer\n * @property {object} data\n * @property {String} data.layer The fully qualified ID of the layer emitting this event\n * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum\n * element. It reflects all namespaces and data operations used by that layer.\n * @see event:any_lz_event\n */\n\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this._initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this._panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this._remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this._interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this._event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this._event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this._panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this._panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id]._layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer._layer_state;\n delete this.layout.state[layer._state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n const target_panel = this.panels[id];\n if (!target_panel) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this._panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n target_panel.loader.hide();\n target_panel.toolbar.destroy(true);\n target_panel.curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (target_panel.svg.container) {\n target_panel.svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(target_panel._layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id]._layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the\n * structure of the data, instead of just displaying something.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @listens event:data_from_layer\n * @param {Object} [opts] Options\n * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched.\n * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details.\n * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested,\n * this is usually required in order to specify dependency order and join operations. See data layer documentation for details.\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(opts, success_callback) {\n const { from_layer, namespace, data_operations, onerror } = opts;\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = onerror || function (err) {\n console.error('An error occurred while acting on an external callback', err);\n };\n\n if (from_layer) {\n // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places.\n const base_prefix = `${this.getBaseId()}.`;\n // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code.\n const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`;\n\n // Ensure that a valid layer exists to watch\n let is_valid_layer = false;\n for (let p of Object.values(this.panels)) {\n is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target);\n if (is_valid_layer) {\n break;\n }\n }\n if (!is_valid_layer) {\n throw new Error(`Could not subscribe to unknown data layer ${layer_target}`);\n }\n\n const listener = (eventData) => {\n if (eventData.data.layer !== layer_target) {\n // Same event name fires for many layers; only fire success cb for the one layer we want\n return;\n }\n try {\n success_callback(eventData.data.content, this);\n } catch (error) {\n error_callback(error);\n }\n };\n\n this.on('data_from_layer', listener);\n return listener;\n }\n\n // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of\n // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized\n // in connection to some other visualization)\n if (!namespace) {\n throw new Error(\"subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option\");\n }\n\n const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot.\n const listener = () => {\n try {\n // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely,\n // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame.\n this.lzd.getData(this.state, entities, dependencies)\n .then((new_data) => success_callback(new_data, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this._remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this._remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this._remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this._initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /**\n * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another,\n * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties,\n * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.\n * @public\n */\n mutateLayout() {\n Object.values(this.panels).forEach((panel) => {\n Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout());\n });\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n const { _interaction } = this;\n if (panel_id) {\n return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(_interaction.dragging || _interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this._panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this._initialized) {\n this._panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let panel of Object.values(this.panels)) {\n if (panel.layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_layout = panel.layout;\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel_layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel_layout.margin.right, 0);\n panel_layout.width += delta;\n panel_layout.margin.left = x_linked_margins.left;\n panel_layout.margin.right = x_linked_margins.right;\n panel_layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n const resize_listener = () => this.rescaleSVG();\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener\n // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed\n if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers\n const options = { root: document.documentElement, threshold: 0.9 };\n const observer = new IntersectionObserver((entries, observer) => {\n if (entries.some((entry) => entry.intersectionRatio > 0)) {\n this.rescaleSVG();\n }\n }, options);\n // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup\n observer.observe(this.container);\n }\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this._mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this._panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent._panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent._panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this._panel_boundaries.hide_timeout);\n this._panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this._panel_boundaries.hide_timeout = setTimeout(() => {\n this._panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this._mouse_guide.vertical.attr('x', -1);\n this._mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this._mouse_guide.vertical.attr('x', coords[0]);\n this._mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n const { _interaction } = this;\n if (_interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x;\n _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y;\n this.panels[_interaction.panel_id].render();\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this._initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this._interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n const { _interaction } = this;\n if (!_interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[_interaction.panel_id] != 'object') {\n this._interaction = {};\n return this;\n }\n const panel = this.panels[_interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel._data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (_interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (_interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (_interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(_interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this._interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} value value\n */\nconst if_value = (parameters, value) => {\n if (typeof value == 'undefined' || parameters.field_value !== value) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} value value\n * @returns {*}\n */\nconst numerical_bin = (parameters, value) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+value < prev || (+value >= prev && +value < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @function ordinal_cycle\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n *\n * @function stable_choice\n *\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, value) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return nullval;\n }\n if (+value <= parameters.breaks[0]) {\n return values[0];\n } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +value && breaks[idx] >= +value) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\n/**\n * Calculate the effect direction based on beta, or the combination of beta and standard error.\n * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.\n *\n * @function effect_direction\n * @param parameters\n * @param parameters.'+' The value to return if the effect direction is positive\n * @param parameters.'-' The value to return if the effect direction is positive\n * @param parameters.beta_field The name of the field containing beta\n * @param parameters.stderr_beta_field The name of the field containing stderr_beta\n * @param {Object} input This function should receive the entire datum object, rather than one single field\n * @returns {null}\n */\nfunction effect_direction(parameters, input) {\n if (input === undefined) {\n return null;\n }\n\n const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters;\n\n if (!beta_field || !stderr_beta_field) {\n throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`);\n }\n\n const beta_val = input[beta_field];\n const se_val = input[stderr_beta_field];\n\n if (beta_val !== undefined) {\n if (se_val !== undefined) {\n if ((beta_val - 1.96 * se_val) > 0) {\n return plus_result;\n } else if ((beta_val + 1.96 * se_val) < 0) {\n return neg_result || null;\n }\n } else {\n if (beta_val > 0) {\n return plus_result;\n } else if (beta_val < 0) {\n return neg_result;\n }\n }\n }\n // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid\n // about expected data formats for layouts.\n return null;\n}\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, findFields, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n/**\n * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.\n * @property {module:LocusZoom_DataFunctions|'fetch'} type\n * @property {String[]} [from] For operations of type \"fetch\", this is required. By default, it will fill in any items provided in \"namespace\" (everything specified in namespace triggers an adapter/network request)\n * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace).\n * Eg, for ld to be fetched after association data, specify \"ld(assoc)\". Most LocusZoom examples fill in all items, in order to make the examples more clear.\n * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation.\n * Eg, if the retrieved data is combined via several left joins in series: `name: \"assoc_plus_ld\"` -> feeds into `{name: \"final\", requires: [\"assoc_plus_ld\"]}`\n * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just\n * the names of other namespaces (or data operations) whose results must be available before a join can be performed\n * @property {String[]} params Any user-defined parameters that should be passed to the particular join function\n * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']`\n * Separate from this section, data functions will also receive a copy of \"plot.state\" automatically.\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or \"line\" for a path.\n * A special \"ribbon\" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD)\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {'vertical'|'horizontal'} [orientation='vertical'] For shape \"ribbon\", specifies whether to draw the ribbon vertically or horizontally.\n * @property {Array} [tick_labels] For shape \"ribbon\", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops.\n * @property {String[]} [color_stops] For shape \"ribbon\", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels.\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n namespace: {},\n data_operations: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is\n * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely\n * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is\n * your job to assure that all of the expected fields are present in every element)\n * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data (\"assoc\")\n * to the globally unique name for something defined in LocusZoom.DataSources.\n * Namespaces allow a single layout to be reused to plot many tracks of the same type: \"given some form of association data, plot it\".\n * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse\n * a layout with a new provider of data- like plotting two association studies stacked together-\n * only the namespace section of the layout needs to be overridden.\n * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})`\n * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions})\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this._layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this._state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this._layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this._tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this._global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n\n // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval\n this._data_contract = new Set(); // List of all fields requested by the layout\n this._entities = new Map();\n this._dependencies = [];\n this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout\n }\n\n /****** Public interface: methods for manipulating the layer from other parts of LZ */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index + 1]) {\n layer_order[current_index] = layer_order[current_index + 1];\n layer_order[current_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index - 1]) {\n layer_order[current_index] = layer_order[current_index - 1];\n layer_order[current_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this._layer_state.extra_fields[id]) {\n this._layer_state.extra_fields[id] = {};\n }\n this._layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /**\n * A list of operations that should be run when the layout is mutated\n * Typically, these are things done once when a layout is first specified, that would not automatically\n * update when the layout was changed.\n * @public\n */\n mutateLayout() {\n // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract.\n if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester\n const { namespace, data_operations } = this.layout;\n this._data_contract = findFields(this.layout, Object.keys(namespace));\n const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this);\n this._entities = entities;\n this._dependencies = dependencies;\n }\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n *\n * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that\n * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data),\n * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}`\n * @protected\n * @param {Object} element The data associated with a particular element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression\n const id_field = this.layout.id_field;\n let value = element[id_field];\n if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) {\n // No field value was found directly, but if it looks like a template expression, next, try parsing that\n // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot!\n value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient\n }\n if (value === null || value === undefined) {\n // Neither exact field nor template options produced an ID\n throw new Error('Unable to generate element ID');\n }\n const element_id = value.toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Abstract method. It should be overridden by data layers that implement separate status\n * nodes, such as genes or intervals.\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or\n * a group of unrelated intervals (all markings grouped within a category).\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n\n // Does the data from the API satisfy the list of fields expected by this layout?\n // Not every record will have every possible field (example: left joins like assoc + ld). The check is \"did\n // we see this field at least once in any record at all\".\n if (this.data.length && this._data_contract.size) {\n const fields_unseen = new Set(this._data_contract);\n for (let record of this.data) {\n Object.keys(record).forEach((field) => fields_unseen.delete(field));\n if (!fields_unseen.size) {\n // Once every requested field has been seen in at least one record, no need to look at more records\n break;\n }\n }\n if (fields_unseen.size) {\n // Current implementation is a soft warning, so that certain \"incremental enhancement\" features\n // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info.\n // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data.\n console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in \"data_operations\" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`);\n }\n }\n\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value);\n }\n\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this._layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const _layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = _layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this._state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this._state_id] = _layer_state;\n }\n this._layer_state = _layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this._tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this._tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this._layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this._tooltips[id].selector.html('');\n this._tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this._tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this._tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this._tooltips[id]) {\n if (typeof this._tooltips[id].selector == 'object') {\n this._tooltips[id].selector.remove();\n }\n delete this._tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this._layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this._tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this._tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this._tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this._tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n const tooltip_layout = this.layout.tooltip;\n if (typeof tooltip_layout != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof tooltip_layout.show == 'string') {\n show_directive = { and: [ tooltip_layout.show ] };\n } else if (typeof tooltip_layout.show == 'object') {\n show_directive = tooltip_layout.show;\n }\n\n let hide_directive = {};\n if (typeof tooltip_layout.hide == 'string') {\n hide_directive = { and: [ tooltip_layout.hide ] };\n } else if (typeof tooltip_layout.hide == 'object') {\n hide_directive = tooltip_layout.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const _layer_state = this._layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (_layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this._layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this._layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this._layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this._layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self._state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies)\n .then((new_data) => {\n this.data = new_data;\n this.applyDataMethods();\n this._initialized = true;\n // Allow listeners (like subscribeToData) to see the information associated with a layer\n this.parent.emit(\n 'data_from_layer',\n { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin\n true\n );\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 15,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n // FIXME: Make gene text font sizes scalable\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n tooltip: null,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this._global_statuses).forEach((global_status) => {\n if (this._global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n data_layer._label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this._label_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer._label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer._label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer._label_texts.nodes();\n data_layer._label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this._label_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this._label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this._label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this._label_texts) {\n this._label_texts.remove();\n }\n\n this._label_texts = this._label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this._label_lines) {\n this._label_lines.remove();\n }\n this._label_lines = this._label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this._label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this._label_texts) {\n this._label_texts.remove();\n }\n if (this._label_lines) {\n this._label_lines.remove();\n }\n if (this._label_groups) {\n this._label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this._label_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{assoc:variant|htmlescape}}
    \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
    \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
    `,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

    {{gene_name|htmlescape}}

    '\n + 'Gene ID: {{gene_id|htmlescape}}
    '\n + 'Transcript ID: {{transcript_id|htmlescape}}
    '\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
    ConstraintExpected variantsObserved variantsConst. Metric
    Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
    o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
    Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
    o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
    pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
    o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

    {{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{catalog:variant|htmlescape}}
    '\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
    '\n + 'Top Trait: {{catalog:trait|htmlescape}}
    '\n + 'Top P Value: {{catalog:log_pvalue|logtoscinotation}}
    '\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
    ' +\n '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
    ' +\n 'Promoter
    ' +\n '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
    ' +\n '{{#if access:target}}Target: {{access:target|htmlescape}}
    {{/if}}' +\n 'Score: {{access:score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n id: 'recombrate',\n namespace: { 'recomb': 'recomb' },\n data_operations: [\n { type: 'fetch', from: ['recomb'] },\n ],\n type: 'line',\n tag: 'recombination',\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: 'recomb:position',\n },\n y_axis: {\n axis: 2,\n field: 'recomb:recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)'],\n },\n {\n type: 'left_match',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'ld'],\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n id_field: 'assoc:variant',\n coalesce: {\n active: true,\n },\n point_shape: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 'diamond',\n },\n },\n {\n // Not every dataset will provide these params\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'assoc:beta',\n stderr_beta_field: 'assoc:se',\n },\n },\n 'circle',\n ],\n point_size: {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: 'ld:correlation',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8'\n {\n shape: 'ribbon',\n orientation: 'vertical',\n width: 10,\n height: 15,\n color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0],\n },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: 'assoc:position',\n },\n y_axis: {\n axis: 1,\n field: 'assoc:log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n namespace: { 'access': 'access' },\n data_operations: [\n { type: 'fetch', from: ['access'] },\n ],\n match: { send: 'access:target', receive: 'access:target' },\n // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.\n id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',\n filters: [\n { field: 'access:score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: 'access:start1',\n field2: 'access:start2',\n },\n y_axis: {\n axis: 1,\n field: 'access:score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n\n base.data_operations.push({\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_catalog',\n requires: ['assoc_plus_ld', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n });\n\n base.tooltip.html += '{{#if catalog:rsid}}
    See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n return base;\n}();\n\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n namespace: { 'phewas': 'phewas' },\n data_operations: [\n { type: 'fetch', from: ['phewas'] },\n ],\n point_shape: [\n {\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'phewas:beta',\n stderr_beta_field: 'phewas:se',\n },\n },\n 'circle',\n ],\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',\n x_axis: {\n field: 'lz_auto_x', // Automatically added by the category_scatter layer\n category_field: 'phewas:trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: 'phewas:log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: 'phewas:trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Trait: {{phewas:trait_label|htmlescape}}
    \nTrait Category: {{phewas:trait_group|htmlescape}}
    \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
    β: {{phewas:beta|scinotation|htmlescape}}
    {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}`,\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{phewas:trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: 'phewas:log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n data_operations: [\n {\n type: 'fetch',\n from: ['gene', 'constraint(gene)'],\n },\n {\n name: 'gene_constraint',\n type: 'genes_to_gnomad_constraint',\n requires: ['gene', 'constraint'],\n },\n ],\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n data_operations: [\n {\n type: 'fetch', from: ['assoc', 'catalog'],\n },\n {\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n },\n ],\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: '#0000CC',\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'catalog:rsid', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n }\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 300,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 46,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 75, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 40,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{catalog:trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: 'catalog:trait', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: 'ld:correlation', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '12px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 55, bottom: 20, left: 70 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu)\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 55, bottom: 120, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 55, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel)\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or applying namespaces.\n let base = super.get(type).get(name);\n\n // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.\n // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)\n const custom_namespaces = overrides.namespace;\n if (!base.namespace) {\n // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout\n // NOTE: The \"merge namespace\" behavior means that data layers can add new data easily, but this method\n // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).\n delete overrides.namespace;\n }\n let result = merge(overrides, base);\n\n if (custom_namespaces) {\n result = applyNamespaces(result, custom_namespaces);\n }\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n\n // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested\n // from external sources. This is purely a hint, because not every layout is generated through the registry.\n if (type === 'data_layer' && copy.namespace) {\n copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();\n }\n\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","/**\n * \"Data operation\" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results\n *\n * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation\n * is a \"join\", such as combining association + LD together into a single set of records for plotting. Several join\n * functions (that operate by analogy to SQL) are provided built-in.\n *\n * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):\n * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.\n * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,\n * this is the recommended path to do so)\n * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),\n * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,\n * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks\n * (PheWAS), automatic panel legends or color schemes (BED tracks), etc.\n *\n * Usually, a data operation receives two recordsets (the left and right members of the join, like \"assoc\" and \"ld\").\n * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network\n * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is\n * uncommon. (if possible, try to provide your data with fewer adapters/network requests!)\n *\n * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,\n * particularly for advanced features, may carry assumptions about field names/ formatting.\n * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,\n * or it may match two datasets based on a specific way of identifying the variant)\n *\n * @module LocusZoom_DataFunctions\n */\nimport {joins} from 'undercomplicate';\n\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"data join\" functions.\n * @alias module:LocusZoom~DataFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\nfunction _wrap_join(handle) {\n // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).\n\n // Many of our join functions are implemented with a different number of arguments than what a datafunction\n // actually receives. (eg, a join function is generic and doesn't care about \"context\" information like plot.state)\n // This wrapper is simple shared code to handle required validation and conversion stuff.\n return (context, deps, ...params) => {\n if (deps.length !== 2) {\n throw new Error('Join functions must receive exactly two recordsets');\n }\n return handle(...deps, ...params);\n };\n}\n\n// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to\n// pick the most significant claim in the catalog for a variant, rather than joining every possible match.\n// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.\nfunction assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {\n if (!assoc_data.length) {\n return assoc_data;\n }\n\n // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each\n const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);\n\n const catalog_flat = []; // Store only the top significant claim for each catalog variant entry\n for (let claims of catalog_by_variant.values()) {\n // Find max item within this set of claims, push that to catalog_\n let best = 0;\n let best_variant;\n for (let item of claims) {\n const val = item[catalog_logp_name];\n if ( val >= best) {\n best_variant = item;\n best = val;\n }\n }\n best_variant.n_catalog_matches = claims.length;\n catalog_flat.push(best_variant);\n }\n return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);\n}\n\n// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.\nfunction genes_to_gnomad_constraint(genes_data, constraint_data) {\n genes_data.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return genes_data;\n}\n\n\n/**\n * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset\n *\n * @function\n * @name left_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('left_match', _wrap_join(joins.left_match));\n\n/**\n * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.\n *\n * @function\n * @name inner_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('inner_match', _wrap_join(joins.inner_match));\n\n/**\n * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.\n *\n * @function\n * @name full_outer_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('full_outer_match', _wrap_join(joins.full_outer_match));\n\n/**\n * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.\n *\n * @function\n * @name assoc_to_gwas_catalog\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: assoc records, then catalog records\n * @param {String} assoc_key The name of the key field in association data, eg variant ID\n * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID\n * @param {String} catalog_log_p_name The name of the \"log_pvalue\" field in gwas catalog data, used to choose the most significant claim for a given variant\n */\nregistry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));\n\n/**\n * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).\n *\n * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.\n *\n * @function\n * @name genes_to_gnomad_constraint\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data\n */\nregistry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));\n\nexport default registry;\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n DATA_OPS as DataFunctions,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n WIDGETS as Widgets,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n DataFunctions,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/./node_modules/@hapi/hoek/lib/assert.js","webpack://[name]/./node_modules/@hapi/hoek/lib/error.js","webpack://[name]/./node_modules/@hapi/hoek/lib/stringify.js","webpack://[name]/./node_modules/@hapi/topo/lib/index.js","webpack://[name]/./node_modules/just-clone/index.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./esm/data/undercomplicate/lru_cache.js","webpack://[name]/./esm/data/undercomplicate/util.js","webpack://[name]/./esm/data/undercomplicate/requests.js","webpack://[name]/./esm/data/undercomplicate/joins.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./esm/data/undercomplicate/adapter.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/registry/data_ops.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["AssertError","module","exports","condition","args","length","Error","Stringify","super","filter","arg","map","message","join","captureStackTrace","this","assert","JSON","stringify","apply","err","Assert","internals","_items","nodes","options","before","concat","after","group","sort","includes","Array","isArray","node","item","seq","push","manual","valid","_sort","others","other","Object","assign","mergeSort","i","graph","graphAfters","create","groups","expandedGroups","graphNodeItem","ancestors","children","child","visited","sorted","next","j","shouldSeeCount","seenCount","k","seqIndex","value","sortedItem","a","b","getRegExpFlags","regExp","source","flags","global","ignoreCase","multiline","sticky","unicode","clone","obj","result","key","type","toString","call","slice","Date","getTime","RegExp","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","prop","prototype","hasOwnProperty","r","Symbol","toStringTag","RegistryBase","Map","name","has","override","set","delete","from","keys","ClassRegistry","parent_name","source_name","overrides","console","warn","arguments","base","sub","add","LLNode","metadata","prev","LRUCache","max_size","_max_size","_cur_size","_store","_head","_tail","cached","prior","_remove","old","match_callback","data","getLinkedData","shared_options","entities","dependencies","consolidate","parsed","spec","exec","name_alone","name_deps","deps","split","_parse_declaration","dag","toposort","entries","e","order","responses","provider","depends_on","this_result","Promise","all","then","prior_results","_provider_name","getData","values","all_results","groupBy","records","group_key","item_group","_any_match","left","right","left_key","right_key","right_index","results","left_match_value","right_matches","right_item","left_index","right_match_value","left_match","REGEX_MARKER","parseMarker","test","match","BaseApiAdapter","BaseLZAdapter","config","_config","cache_enabled","cache_size","_enable_cache","_cache","dependent_data","response_text","_buildRequestOptions","cache_key","_getCacheKey","resolve","_performRequest","text","_normalizeResponse","_cache_meta","catch","remove","_annotateRecords","_postProcessResponse","_url","url","_getURL","fetch","response","ok","statusText","parse","params","prefix_namespace","limit_fields","_prefix_namespace","_limit_fields","Set","chr","start","end","superset","find","md","row","reduce","acc","label","a_record","fieldname","suffixer","BaseUMAdapter","_genome_build","genome_build","build","constructor","N","every","fields","record","AssociationLZ","_source_id","request_options","GwasCatalogLZ","_validateBuildSource","source_query","GeneLZ","GeneConstraintLZ","state","genes_data","unique_gene_names","gene","gene_name","query","replace","body","method","headers","LDServer","assoc_data","assoc_variant_name","_findPrefixedKey","assoc_logp_name","refvar","best_hit","ldrefvar","best_logp","variant","log_pvalue","lz_is_ld_refvar","chrom","pos","ref","alt","coord","String","__find_ld_refvar","_skip_request","ld_refvar","ld_source","ld_population","ld_pop","population","encodeURIComponent","combined","chainRequests","payload","forEach","RecombLZ","StaticSource","_data","PheWASLZ","registry","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","transforms","full_name","field_name","transformations","val","transform","extra","undefined","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","m","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","paths","p","_","__","subject","uniqPaths","arr","elem","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","layout","shared_namespaces","requested_ns","merge","custom_layout","default_layout","property","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","findFields","prefixes","field_finder","all_ns","value_type","a_match","renameField","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","regex","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","DataOperation","join_type","initiator","_callable","_initiator","_params","plot_state","dependent_recordsets","data_layer","sources","_sources","namespace_options","data_operations","namespace_local_names","dependency_order","unshift","ns_pattern","local_name","global_name","dep_spec","requires","namecount","require_name","task","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","parentNode","insert","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","x","width","delay","setTimeout","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","position","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","index","indexOf","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","element","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","_panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","radioId","has_option","choice","defaultName","default_config_display_name","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","addWidget","widget","error","_panel_boundaries","dragging","_interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","line_height","_data_layer_ids_by_z_index","reverse","layer_legend","label_x","label_y","shape_factory","path_y","is_horizontal","color_stops","all_elements","ribbon_group","axis_group","axis_offset","tick_labels","range","scale","domain","axis","tickSize","tickValues","tickFormat","v","to_next_marking","radius","PI","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","_initialized","_layout_idx","_state_id","_data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","_zoom_timeout","_event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","parseFloat","y_axis","z_index","dlid","idx","layout_idx","data_layer_layout","target_layer","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","axes_config","base_x_range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","current_drag","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","namespace","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tick_format","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","panel_boundaries","mouse_guide","Plot","datasource","_remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","_layer_state","_setDefaultState","target_panel","clearPanelData","opts","success_callback","from_layer","onerror","error_callback","base_prefix","layer_target","startsWith","is_valid_layer","some","listener","config_to_sources","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","mutateLayout","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","resize_listener","rescaleSVG","window","addEventListener","trackExternalListener","IntersectionObserver","threshold","observer","entry","intersectionRatio","observe","load_listener","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","_mouse_guide","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","ret","positionStringToInt","suffixre","mult","tokens","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","field_value","numerical_bin","breaks","null_value","curr","categorical_bin","categories","ordinal_cycle","stable_choice","max_cache_size","clear","hash","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","effect_direction","input","beta_field","stderr_beta_field","plus_result","neg_result","beta_val","se_val","id_field","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","_tooltips","_global_statuses","_data_contract","_entities","_dependencies","layer_order","current_index","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","fields_unseen","debug","lz_is_match","getDataLayer","getPanel","getPlot","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","tooltip_layout","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","_label_texts","da","dal","_label_lines","dax","abound","bbound","_label_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","_label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","version","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","custom_namespaces","_auto_fields","contents","list","_wrap_join","handle","catalog_data","assoc_key","catalog_key","catalog_logp_name","catalog_by_variant","catalog_flat","claims","best_variant","best","n_catalog_matches","constraint_data","alias","constraint","LocusZoom","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","source_id","Adapters","DataLayers","DataFunctions","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["'use strict';\n\nconst AssertError = require('./error');\n\nconst internals = {};\n\n\nmodule.exports = function (condition, ...args) {\n\n if (condition) {\n return;\n }\n\n if (args.length === 1 &&\n args[0] instanceof Error) {\n\n throw args[0];\n }\n\n throw new AssertError(args);\n};\n","'use strict';\n\nconst Stringify = require('./stringify');\n\n\nconst internals = {};\n\n\nmodule.exports = class extends Error {\n\n constructor(args) {\n\n const msgs = args\n .filter((arg) => arg !== '')\n .map((arg) => {\n\n return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);\n });\n\n super(msgs.join(' ') || 'Unknown error');\n\n if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$\n Error.captureStackTrace(this, exports.assert);\n }\n }\n};\n","'use strict';\n\nconst internals = {};\n\n\nmodule.exports = function (...args) {\n\n try {\n return JSON.stringify.apply(null, args);\n }\n catch (err) {\n return '[Cannot display object: ' + err.message + ']';\n }\n};\n","'use strict';\n\nconst Assert = require('@hapi/hoek/lib/assert');\n\n\nconst internals = {};\n\n\nexports.Sorter = class {\n\n constructor() {\n\n this._items = [];\n this.nodes = [];\n }\n\n add(nodes, options) {\n\n options = options || {};\n\n // Validate rules\n\n const before = [].concat(options.before || []);\n const after = [].concat(options.after || []);\n const group = options.group || '?';\n const sort = options.sort || 0; // Used for merging only\n\n Assert(!before.includes(group), `Item cannot come before itself: ${group}`);\n Assert(!before.includes('?'), 'Item cannot come before unassociated items');\n Assert(!after.includes(group), `Item cannot come after itself: ${group}`);\n Assert(!after.includes('?'), 'Item cannot come after unassociated items');\n\n if (!Array.isArray(nodes)) {\n nodes = [nodes];\n }\n\n for (const node of nodes) {\n const item = {\n seq: this._items.length,\n sort,\n before,\n after,\n group,\n node\n };\n\n this._items.push(item);\n }\n\n // Insert event\n\n if (!options.manual) {\n const valid = this._sort();\n Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');\n }\n\n return this.nodes;\n }\n\n merge(others) {\n\n if (!Array.isArray(others)) {\n others = [others];\n }\n\n for (const other of others) {\n if (other) {\n for (const item of other._items) {\n this._items.push(Object.assign({}, item)); // Shallow cloned\n }\n }\n }\n\n // Sort items\n\n this._items.sort(internals.mergeSort);\n for (let i = 0; i < this._items.length; ++i) {\n this._items[i].seq = i;\n }\n\n const valid = this._sort();\n Assert(valid, 'merge created a dependencies error');\n\n return this.nodes;\n }\n\n sort() {\n\n const valid = this._sort();\n Assert(valid, 'sort created a dependencies error');\n\n return this.nodes;\n }\n\n _sort() {\n\n // Construct graph\n\n const graph = {};\n const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives\n const groups = Object.create(null);\n\n for (const item of this._items) {\n const seq = item.seq; // Unique across all items\n const group = item.group;\n\n // Determine Groups\n\n groups[group] = groups[group] || [];\n groups[group].push(seq);\n\n // Build intermediary graph using 'before'\n\n graph[seq] = item.before;\n\n // Build second intermediary graph with 'after'\n\n for (const after of item.after) {\n graphAfters[after] = graphAfters[after] || [];\n graphAfters[after].push(seq);\n }\n }\n\n // Expand intermediary graph\n\n for (const node in graph) {\n const expandedGroups = [];\n\n for (const graphNodeItem in graph[node]) {\n const group = graph[node][graphNodeItem];\n groups[group] = groups[group] || [];\n expandedGroups.push(...groups[group]);\n }\n\n graph[node] = expandedGroups;\n }\n\n // Merge intermediary graph using graphAfters into final graph\n\n for (const group in graphAfters) {\n if (groups[group]) {\n for (const node of groups[group]) {\n graph[node].push(...graphAfters[group]);\n }\n }\n }\n\n // Compile ancestors\n\n const ancestors = {};\n for (const node in graph) {\n const children = graph[node];\n for (const child of children) {\n ancestors[child] = ancestors[child] || [];\n ancestors[child].push(node);\n }\n }\n\n // Topo sort\n\n const visited = {};\n const sorted = [];\n\n for (let i = 0; i < this._items.length; ++i) { // Looping through item.seq values out of order\n let next = i;\n\n if (ancestors[i]) {\n next = null;\n for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values\n if (visited[j] === true) {\n continue;\n }\n\n if (!ancestors[j]) {\n ancestors[j] = [];\n }\n\n const shouldSeeCount = ancestors[j].length;\n let seenCount = 0;\n for (let k = 0; k < shouldSeeCount; ++k) {\n if (visited[ancestors[j][k]]) {\n ++seenCount;\n }\n }\n\n if (seenCount === shouldSeeCount) {\n next = j;\n break;\n }\n }\n }\n\n if (next !== null) {\n visited[next] = true;\n sorted.push(next);\n }\n }\n\n if (sorted.length !== this._items.length) {\n return false;\n }\n\n const seqIndex = {};\n for (const item of this._items) {\n seqIndex[item.seq] = item;\n }\n\n this._items = [];\n this.nodes = [];\n\n for (const value of sorted) {\n const sortedItem = seqIndex[value];\n this.nodes.push(sortedItem.node);\n this._items.push(sortedItem);\n }\n\n return true;\n }\n};\n\n\ninternals.mergeSort = (a, b) => {\n\n return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);\n};\n","module.exports = clone;\n\n/*\n Deep clones all properties except functions\n\n var arr = [1, 2, 3];\n var subObj = {aa: 1};\n var obj = {a: 3, b: 5, c: arr, d: subObj};\n var objClone = clone(obj);\n arr.push(4);\n subObj.bb = 2;\n obj; // {a: 3, b: 5, c: [1, 2, 3, 4], d: {aa: 1}}\n objClone; // {a: 3, b: 5, c: [1, 2, 3], d: {aa: 1, bb: 2}}\n*/\n\nfunction clone(obj) {\n if (typeof obj == 'function') {\n return obj;\n }\n var result = Array.isArray(obj) ? [] : {};\n for (var key in obj) {\n // include prototype properties\n var value = obj[key];\n var type = {}.toString.call(value).slice(8, -1);\n if (type == 'Array' || type == 'Object') {\n result[key] = clone(value);\n } else if (type == 'Date') {\n result[key] = new Date(value.getTime());\n } else if (type == 'RegExp') {\n result[key] = RegExp(value.source, getRegExpFlags(value));\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction getRegExpFlags(regExp) {\n if (typeof regExp.source.flags == 'string') {\n return regExp.source.flags;\n } else {\n var flags = [];\n regExp.global && flags.push('g');\n regExp.ignoreCase && flags.push('i');\n regExp.multiline && flags.push('m');\n regExp.sticky && flags.push('y');\n regExp.unicode && flags.push('u');\n return flags.join('');\n }\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.14.0-beta.3';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","/**\n * Implement an LRU Cache\n */\n\n\nclass LLNode {\n /**\n * A single node in the linked list. Users will only need to deal with this class if using \"approximate match\" (`cache.find()`)\n * @memberOf module:undercomplicate\n * @param {string} key\n * @param {*} value\n * @param {object} metadata\n * @param {LLNode} prev\n * @param {LLNode} next\n */\n constructor(key, value, metadata = {}, prev = null, next = null) {\n this.key = key;\n this.value = value;\n this.metadata = metadata;\n this.prev = prev;\n this.next = next;\n }\n}\n\nclass LRUCache {\n /**\n * Least-recently used cache implementation, with \"approximate match\" semantics\n * @memberOf module:undercomplicate\n * @param {number} [max_size=3]\n */\n constructor(max_size = 3) {\n this._max_size = max_size;\n this._cur_size = 0; // replace with map.size so we aren't managing manually?\n this._store = new Map();\n\n // Track LRU state\n this._head = null;\n this._tail = null;\n\n // Validate options\n if (max_size === null || max_size < 0) {\n throw new Error('Cache \"max_size\" must be >= 0');\n }\n }\n\n /**\n * Check key membership without updating LRU\n * @param key\n * @returns {boolean}\n */\n has(key) {\n return this._store.has(key);\n }\n\n /**\n * Retrieve value from cache (if present) and update LRU cache to say an item was recently used\n * @param key\n * @returns {null|*}\n */\n get(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return null;\n }\n if (this._head !== cached) {\n // Rewrite the cached value to ensure it is head of the list\n this.add(key, cached.value);\n }\n return cached.value;\n }\n\n /**\n * Add an item. Forcibly replaces the existing cached value for the same key.\n * @param key\n * @param value\n * @param {Object} [metadata={}) Metadata associated with an item. Metadata can be used for lookups (`cache.find`) to test for a cache hit based on non-exact match\n */\n add(key, value, metadata = {}) {\n if (this._max_size === 0) {\n // Don't cache items if cache has 0 size.\n return;\n }\n\n const prior = this._store.get(key);\n if (prior) {\n this._remove(prior);\n }\n\n const node = new LLNode(key, value, metadata, null, this._head);\n\n if (this._head) {\n this._head.prev = node;\n } else {\n this._tail = node;\n }\n\n this._head = node;\n this._store.set(key, node);\n\n if (this._max_size >= 0 && this._cur_size >= this._max_size) {\n const old = this._tail;\n this._tail = this._tail.prev;\n this._remove(old);\n }\n this._cur_size += 1;\n }\n\n\n // Cache manipulation methods\n clear() {\n this._head = null;\n this._tail = null;\n this._cur_size = 0;\n this._store = new Map();\n }\n\n // Public method, remove by key\n remove(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return false;\n }\n this._remove(cached);\n return true;\n }\n\n // Internal implementation, useful when working on list\n _remove(node) {\n if (node.prev !== null) {\n node.prev.next = node.next;\n } else {\n this._head = node.next;\n }\n\n if (node.next !== null) {\n node.next.prev = node.prev;\n } else {\n this._tail = node.prev;\n }\n this._store.delete(node.key);\n this._cur_size -= 1;\n }\n\n /**\n * Find a matching item in the cache, or return null. This is useful for \"approximate match\" semantics,\n * to check if something qualifies as a cache hit despite not having the exact same key.\n * (Example: zooming into a region, where the smaller region is a subset of something already cached)\n * @param {function} match_callback A function to be used to test the node as a possible match (returns true or false)\n * @returns {null|LLNode}\n */\n find(match_callback) {\n let node = this._head;\n while (node) {\n const next = node.next;\n if (match_callback(node)) {\n return node;\n }\n node = next;\n }\n }\n}\n\nexport { LRUCache };\n","/**\n * @private\n */\n\nimport justclone from 'just-clone';\n\n/**\n * The \"just-clone\" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.\n * @param {object} data\n * @returns {*}\n */\nfunction clone(data) {\n if (typeof data !== 'object') {\n return data;\n }\n return justclone(data);\n}\n\nexport { clone };\n","/**\n * Perform a series of requests, respecting order of operations\n * @private\n */\n\nimport {Sorter} from '@hapi/topo';\n\n\nfunction _parse_declaration(spec) {\n // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.\n const parsed = /^(?\\w+)$|((?\\w+)+\\(\\s*(?[^)]+?)\\s*\\))/.exec(spec);\n if (!parsed) {\n throw new Error(`Unable to parse dependency specification: ${spec}`);\n }\n\n let {name_alone, name_deps, deps} = parsed.groups;\n if (name_alone) {\n return [name_alone, []];\n }\n\n deps = deps.split(/\\s*,\\s*/);\n return [name_deps, deps];\n}\n\n/**\n * Perform a request for data from a set of providers, taking into account dependencies between requests.\n * This can be a mix of network requests or other actions (like join tasks), provided that the provider be some\n * object that implements a method `instance.getData`\n *\n * Each data layer in LocusZoom will translate the internal configuration into a format used by this function.\n * This function is never called directly in custom user code. In locuszoom, Requester Handles You\n *\n * TODO Future: It would be great to add a warning if the final element in the DAG does not reference all dependencies. This is a limitation of the current toposort library we use.\n *\n * @param {object} shared_options Options passed globally to all requests. In LocusZoom, this is often a copy of \"plot.state\"\n * @param {Map} entities A lookup of named entities that implement the method `instance.getData -> Promise`\n * @param {String[]} dependencies A description of how to fetch entities, and what they depend on, like `['assoc', 'ld(assoc)']`.\n * **Order will be determined by a DAG and the last item in the DAG is all that is returned.**\n * @param {boolean} [consolidate=true] Whether to return all results (almost never used), or just the last item in the resolved DAG.\n * This can be a pitfall in common usage: if you forget a \"join/consolidate\" task, the last result may appear to be missing some data.\n * @returns {Promise}\n */\nfunction getLinkedData(shared_options, entities, dependencies, consolidate = true) {\n if (!dependencies.length) {\n return [];\n }\n\n const parsed = dependencies.map((spec) => _parse_declaration(spec));\n const dag = new Map(parsed);\n\n // Define the order to perform requests in, based on a DAG\n const toposort = new Sorter();\n for (let [name, deps] of dag.entries()) {\n try {\n toposort.add(name, {after: deps, group: name});\n } catch (e) {\n throw new Error(`Invalid or possible circular dependency specification for: ${name}`);\n }\n }\n const order = toposort.nodes;\n\n // Verify that all requested entities exist by name!\n const responses = new Map();\n for (let name of order) {\n const provider = entities.get(name);\n if (!provider) {\n throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);\n }\n\n // Each promise should only be triggered when the things it depends on have been resolved\n const depends_on = dag.get(name) || [];\n const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));\n\n const this_result = prereq_promises.then((prior_results) => {\n // Each request will be told the name of the provider that requested it. This can be used during post-processing,\n // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)\n // This has a secondary effect: it ensures that any changes made to \"shared\" options in one adapter will\n // not leak out to others via a mutable shared object reference.\n const options = Object.assign({_provider_name: name}, shared_options);\n return provider.getData(options, ...prior_results);\n });\n responses.set(name, this_result);\n }\n return Promise.all([...responses.values()])\n .then((all_results) => {\n if (consolidate) {\n // Some usages- eg fetch + data join tasks- will only require the last response in the sequence\n // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)\n return all_results[all_results.length - 1];\n }\n return all_results;\n });\n}\n\n\nexport {getLinkedData};\n\n// For testing only\nexport {_parse_declaration};\n","/**\n * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.\n */\nimport { clone } from './util';\n\n\n/**\n * Simple grouping function, used to identify sets of records for joining.\n *\n * Used internally by join helpers, exported mainly for unit testing\n * @memberOf module:undercomplicate\n * @param {object[]} records\n * @param {string} group_key\n * @returns {Map}\n */\nfunction groupBy(records, group_key) {\n const result = new Map();\n for (let item of records) {\n const item_group = item[group_key];\n\n if (typeof item_group === 'undefined') {\n throw new Error(`All records must specify a value for the field \"${group_key}\"`);\n }\n if (typeof item_group === 'object') {\n // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)\n throw new Error('Attempted to group on a field with non-primitive values');\n }\n\n let group = result.get(item_group);\n if (!group) {\n group = [];\n result.set(item_group, group);\n }\n group.push(item);\n }\n return result;\n}\n\n\nfunction _any_match(type, left, right, left_key, right_key) {\n // Helper that consolidates logic for all three join types\n const right_index = groupBy(right, right_key);\n const results = [];\n for (let item of left) {\n const left_match_value = item[left_key];\n const right_matches = right_index.get(left_match_value) || [];\n if (right_matches.length) {\n // Record appears on both left and right; equiv to an inner join\n results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));\n } else if (type !== 'inner') {\n // Record appears on left but not right\n results.push(clone(item));\n }\n }\n\n if (type === 'outer') {\n // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side\n const left_index = groupBy(left, left_key);\n for (let item of right) {\n const right_match_value = item[right_key];\n const left_matches = left_index.get(right_match_value) || [];\n if (!left_matches.length) {\n results.push(clone(item));\n }\n }\n }\n return results;\n}\n\n/**\n * Equivalent to LEFT OUTER JOIN in SQL. Return all left records, joined to matching right data where appropriate.\n * @memberOf module:undercomplicate\n * @param {Object[]} left The left side recordset\n * @param {Object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction left_match(left, right, left_key, right_key) {\n return _any_match('left', ...arguments);\n}\n\n/**\n * Equivalent to INNER JOIN in SQL. Only return record joins if the key field has a match on both left and right.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction inner_match(left, right, left_key, right_key) {\n return _any_match('inner', ...arguments);\n}\n\n/**\n * Equivalent to FULL OUTER JOIN in SQL. Return records in either recordset, joined where appropriate.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction full_outer_match(left, right, left_key, right_key) {\n return _any_match('outer', ...arguments);\n}\n\nexport {left_match, inner_match, full_outer_match, groupBy};\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 1 }]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 2 }]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nimport {BaseUrlAdapter} from './undercomplicate';\n\nimport {parseMarker} from '../helpers/parse';\n\n/**\n * Replaced with the BaseLZAdapter class.\n * @public\n * @deprecated\n */\nclass BaseAdapter {\n constructor() {\n throw new Error('The \"BaseAdapter\" and \"BaseApiAdapter\" classes have been replaced in LocusZoom 0.14. See migration guide for details.');\n }\n}\n\n/**\n * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @deprecated\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {}\n\n\n/**\n * @extends module:undercomplicate.BaseUrlAdapter\n * @inheritDoc\n */\nclass BaseLZAdapter extends BaseUrlAdapter {\n /**\n * @param [config.cache_enabled=true]\n * @param [config.cache_size=3]\n * @param [config.url]\n * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name.\n * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant)\n * Typically, this is only disabled if the response payload is very unusual\n * @param {String[]} [config.limit_fields=null] If an API returns far more data than is needed, this can be used to simplify\n * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.\n */\n constructor(config = {}) {\n if (config.params) {\n // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places.\n console.warn('Deprecation warning: all options in \"config.params\" should now be specified as top level keys.');\n Object.assign(config, config.params || {});\n delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality!\n }\n super(config);\n\n // Prefix the namespace for this source to all fieldnames: id -> assoc.id\n // This is useful for almost all layers because the layout object says where to find every field, exactly.\n // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on\n // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear\n // in the response. (gene_name instead of genes.gene_name)\n const { prefix_namespace = true, limit_fields } = config;\n this._prefix_namespace = prefix_namespace;\n this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields\n }\n\n /**\n * Determine how a particular request will be identified in cache. Most LZ requests are region based,\n * so the default is a string concatenation of `chr_start_end`. This adapter is \"region aware\"- if the user\n * zooms in, it won't trigger a network request because we alread have the data needed.\n * @param options Receives plot.state plus any other request options defined by this source\n * @returns {string}\n * @public\n */\n _getCacheKey(options) {\n // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default\n let {chr, start, end} = options; // Current view: plot.state\n\n // Does a prior cache hit qualify as a superset of the ROI?\n const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end);\n if (superset) {\n ({ chr, start, end } = superset.metadata);\n }\n\n // The default cache key is region-based, and this method only returns the region-based part of the cache hit\n // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check\n options._cache_meta = { chr, start, end };\n return `${chr}_${start}_${end}`;\n }\n\n /**\n * Add the \"local namespace\" as a prefix for every field returned for this request. Eg if the association api\n * returns a field called variant, and the source is referred to as \"assoc\" within a particular data layer, then\n * the returned records will have a field called \"assoc:variant\"\n *\n * @param records\n * @param options\n * @returns {*}\n * @public\n */\n _postProcessResponse(records, options) {\n if (!this._prefix_namespace || !Array.isArray(records)) {\n return records;\n }\n\n // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for\n // assoc data might see \"variant\" as \"assoc.variant\"\n const { _limit_fields } = this;\n const { _provider_name } = options;\n\n return records.map((row) => {\n return Object.entries(row).reduce(\n (acc, [label, value]) => {\n // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload.\n if (!_limit_fields || _limit_fields.has(label)) {\n acc[`${_provider_name}:${label}`] = value;\n }\n return acc;\n },\n {},\n );\n });\n }\n\n /**\n * Convenience method, manually called in LZ sources that deal with dependent data.\n *\n * In the last step of fetching data, LZ adds a prefix to each field name.\n * This means that operations like \"build query based on prior data\" can't just ask for \"log_pvalue\" because\n * they are receiving \"assoc:log_pvalue\" or some such unknown prefix.\n *\n * This helper lets us use dependent data more easily. Not every adapter needs to use this method.\n *\n * @param {Object} a_record One record (often the first one in a set of records)\n * @param {String} fieldname The desired fieldname, eg \"log_pvalue\"\n */\n _findPrefixedKey(a_record, fieldname) {\n const suffixer = new RegExp(`:${fieldname}$`);\n const match = Object.keys(a_record).find((key) => suffixer.test(key));\n if (!match) {\n throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`);\n }\n return match;\n }\n}\n\n\n/**\n * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies\n * of one particular web server.\n * @extends module:LocusZoom_Adapters~BaseLZAdapter\n * @inheritDoc\n */\nclass BaseUMAdapter extends BaseLZAdapter {\n /**\n * @param {Object} config\n * @param {String} [config.build] The genome build to be used by all requests for this adapter. (UMich APIs are all genome build aware). \"GRCh37\" or \"GRCh38\"\n */\n constructor(config = {}) {\n super(config);\n // The UM portaldev API accepts an (optional) parameter \"genome_build\"\n this._genome_build = config.genome_build || config.build;\n }\n\n _validateBuildSource(build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${this.constructor.name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`);\n }\n }\n\n // Special behavior for the UM portaldev API: col -> row format normalization\n /**\n * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.\n * @param response_text\n * @param options\n * @returns {Object[]}\n * @public\n */\n _normalizeResponse(response_text, options) {\n let data = super._normalizeResponse(...arguments);\n // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload\n data = data.data || data;\n\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the columns, and create an object for each row record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n}\n\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @inheritDoc\n *\n * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL\n */\nclass AssociationLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files\n const { source } = config;\n this._source_id = source;\n }\n\n _getURL (request_options) {\n const {chr, start, end} = request_options;\n const base = super._getURL(request_options);\n return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`;\n }\n}\n\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GwasCatalogLZ extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build] The genome build to use when requesting the specific genomic region.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n const source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given.\n // We will avoid transforming or modifying the payload.\n this._prefix_namespace = false;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GeneConstraintLZ extends BaseLZAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config = {}) {\n super(config);\n this._prefix_namespace = false;\n }\n\n _buildRequestOptions(state, genes_data) {\n const build = state.genome_build || this._config.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = new Set();\n for (let gene of genes_data) {\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n unique_gene_names.add(gene.gene_name);\n }\n\n state.query = [...unique_gene_names.values()].map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n state.build = build;\n return Object.assign({}, state);\n }\n\n _performRequest(options) {\n let {query, build} = options;\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears to set max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve([]);\n }\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n\n const url = this._getURL(options);\n\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.\n */\n _normalizeResponse(response_text) {\n if (typeof response_text !== 'string') {\n // If the query short-circuits, we receive an empty list instead of a string\n return response_text;\n }\n const data = JSON.parse(response_text);\n return data.data;\n }\n}\n\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant and yse that as the LD reference variant.\n *\n * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly\n * if this information is not provided.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass LDServer extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.method='rsquare'] The metric used to calculate LD\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n __find_ld_refvar(state, assoc_data) {\n const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant');\n const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue');\n\n // Determine the reference variant (via user selected OR automatic-per-track)\n let refvar;\n let best_hit = {};\n if (state.ldrefvar) {\n // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data\n refvar = state.ldrefvar;\n best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {};\n } else {\n // find highest log-value and associated var spec\n let best_logp = 0;\n for (let item of assoc_data) {\n const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item;\n if (log_pvalue > best_logp) {\n best_logp = log_pvalue;\n refvar = variant;\n best_hit = item;\n }\n }\n }\n\n // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting.\n // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase.\n best_hit.lz_is_ld_refvar = true;\n\n // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server,\n // the variant fields must be normalized to a specific format. All later LD operations will use that format.\n const match = parseMarker(refvar, true);\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n\n const [chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip?\n if (ref && alt) {\n refvar += `_${ref}/${alt}`;\n }\n\n const coord = +pos;\n // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably\n // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby.\n if ((coord && state.ldrefvar && state.chr) && (chrom !== String(state.chr) || coord < state.start || coord > state.end)) {\n // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a\n // *copy* of plot.state, so wiping here doesn't remove the original value.\n state.ldrefvar = null;\n return this.__find_ld_refvar(state, assoc_data);\n }\n\n // Return the reference variant, in a normalized format suitable for LDServer queries\n return refvar;\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n // No variants, so no need to annotate association data with LD!\n // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region)\n base._skip_request = true;\n return base;\n }\n\n base.ld_refvar = this.__find_ld_refvar(state, assoc_data);\n\n // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config\n const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let ld_source = state.ld_source || this._config.source || '1000G';\n const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL\n\n if (ld_source === '1000G' && genome_build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n ld_source = '1000G-FRZ09';\n }\n\n this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option\n return Object.assign({}, base, { genome_build, ld_source, ld_population });\n }\n\n _getURL(request_options) {\n const method = this._config.method || 'rsquare';\n const {\n chr, start, end,\n ld_refvar,\n genome_build, ld_source, ld_population,\n } = request_options;\n\n const base = super._getURL(request_options);\n\n return [\n base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(ld_refvar),\n '&chrom=', encodeURIComponent(chr),\n '&start=', encodeURIComponent(start),\n '&stop=', encodeURIComponent(end),\n ].join('');\n }\n\n _getCacheKey(options) {\n // LD is keyed by more than just region; append other parameters to the base cache key\n const base = super._getCacheKey(options);\n const { ld_refvar, ld_source, ld_population } = options;\n return `${base}_${ld_refvar}_${ld_source}_${ld_population}`;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird.\n return Promise.resolve([]);\n }\n\n const url = this._getURL(options);\n\n // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseUMAdapter {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['position', 'recomb_rate'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {object} config.data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseLZAdapter {\n constructor(config = {}) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n super(...arguments);\n const { data } = config;\n if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key\n throw new Error(\"'StaticSource' must provide data as required option 'config.data'\");\n }\n this._data = data;\n }\n\n _performRequest(options) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will\n * be used to provide PheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const base = super._getURL(request_options);\n const url = [\n base,\n \"?filter=variant eq '\", encodeURIComponent(request_options.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n _getCacheKey(options) {\n // Not a region based source; don't do anything smart for cache check\n return this._getURL(options);\n }\n}\n\n// Deprecated symbols\nexport { BaseAdapter, BaseApiAdapter };\n\n// Usually used as a parent class for custom code\nexport { BaseLZAdapter, BaseUMAdapter };\n\n// Usually used as a standalone class\nexport {\n AssociationLZ,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","import {LRUCache} from './lru_cache';\nimport {clone} from './util';\n\n/**\n * @param {boolean} [config.cache_enabled=true] Whether to enable the LRU cache, and store a copy of the normalized and parsed response data.\n * Turned on by default for most remote requests; turn off if you are using another datastore (like Vuex) or if the response body uses too much memory.\n * @param {number} [config.cache_size=3] How many requests to cache. Track-dependent annotations like LD might benefit\n * from caching more items, while very large payloads (like, um, TOPMED LD) might benefit from a smaller cache size.\n * For most LocusZoom usages, the cache is \"region aware\": zooming in will use cached data, not a separate request\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseAdapter {\n constructor(config = {}) {\n this._config = config;\n const {\n // Cache control\n cache_enabled = true,\n cache_size = 3,\n } = config;\n this._enable_cache = cache_enabled;\n this._cache = new LRUCache(cache_size);\n }\n\n /**\n * Build an object with options that control the request. This can take into account both explicit options, and prior data.\n * @param {Object} options Any global options passed in via `getData`. Eg, in locuszoom, every request is passed a copy of `plot.state` as the options object, in which case every adapter would expect certain basic information like `chr, start, end` to be available.\n * @param {Object[]} dependent_data If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, `ld(assoc)` means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.\n * @returns {*} An options object containing initial options, plus any calculated values relevant to the request.\n * @public\n */\n _buildRequestOptions(options, dependent_data) {\n // Perform any pre-processing required that may influence the request. Receives an array with the payloads\n // for each request that preceded this one in the dependency chain\n // This method may optionally take dependent data into account. For many simple adapters, there won't be any dependent data!\n return Object.assign({}, options);\n }\n\n /**\n * Determine how this request is uniquely identified in cache. Usually this is an exact match for the same key, but it doesn't have to be.\n * The LRU cache implements a `find` method, which means that a cache item can optionally be identified by its node\n * `metadata` (instead of exact key match).\n * This is useful for situations where the user zooms in to a smaller region and wants the original request to\n * count as a cache hit. See subclasses for example.\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {*} This is often a string concatenating unique values for a compound cache key, like `chr_start_end`. If null, it is treated as a cache miss.\n * @public\n */\n _getCacheKey(options) {\n /* istanbul ignore next */\n if (this._enable_cache) {\n throw new Error('Method not implemented');\n }\n return null;\n }\n\n /**\n * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {Promise}\n * @public\n */\n _performRequest(options) {\n /* istanbul ignore next */\n throw new Error('Not implemented');\n }\n\n /**\n * Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.\n * @param {*} response_text The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON.\n * @param {Object} options Request options. These are not typically used when normalizing a response, but the object is available.\n * @returns {*} A list of objects, each object representing one row of data `{column_name: value_for_row}`\n * @public\n */\n _normalizeResponse(response_text, options) {\n return response_text;\n }\n\n /**\n * Perform custom client-side operations on the retrieved data. For example, add calculated fields or\n * perform rapid client-side filtering on cached data. Annotations are applied after cache, which means\n * that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.\n *\n * This result is currently not cached, but it may become so in the future as responsibility for dynamic UI\n * behavior moves to other layers of the application.\n * @param {Object[]} records\n * @param {Object} options\n * @returns {*}\n * @public\n */\n _annotateRecords(records, options) {\n return records;\n }\n\n /**\n * A hook to transform the response after all operations are done. For example, this can be used to prefix fields\n * with a namespace unique to the request, like `log_pvalue` -> `assoc:log_pvalue`. (by applying namespace prefixes to field names last,\n * annotations and validation can happen on the actual API payload, without having to guess what the fields were renamed to).\n * @param records\n * @param options\n * @public\n */\n _postProcessResponse(records, options) {\n return records;\n }\n\n /**\n * All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.\n * @param {object} options Shared options for this request. In LocusZoom, this is typically a copy of `plot.state`.\n * @param {Array[]} dependent_data Zero or more recordsets corresponding to each individual adapter that this one depends on.\n * Can be used to build a request that takes into account prior data.\n * @returns {Promise<*>}\n */\n getData(options = {}, ...dependent_data) {\n // Public facing method to define, perform, and process the request\n options = this._buildRequestOptions(options, ...dependent_data);\n\n const cache_key = this._getCacheKey(options);\n\n // Then retrieval and parse steps: parse + normalize response, annotate\n let result;\n if (this._enable_cache && this._cache.has(cache_key)) {\n result = this._cache.get(cache_key);\n } else {\n // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)\n // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry\n // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,\n // even if the actual cache key wasn't an exact match. (see subclasses for an example; this class is generic)\n result = Promise.resolve(this._performRequest(options))\n // Note: we cache the normalized (parsed) response\n .then((text) => this._normalizeResponse(text, options));\n this._cache.add(cache_key, result, options._cache_meta);\n // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request\n // Otherwise, temporary failures couldn't be resolved by trying again in a moment\n // TODO: In the future, consider providing a way to skip requests (eg, a sentinel value to flag something\n // as not cacheable, like \"no dependent data means no request... but maybe in another place this is used, there will be different dependent data and a request would make sense\")\n result.catch((e) => this._cache.remove(cache_key));\n }\n\n return result\n // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache\n .then((data) => clone(data))\n .then((records) => this._annotateRecords(records, options))\n .then((records) => this._postProcessResponse(records, options));\n }\n}\n\n\n/**\n * Fetch data over the web, usually from a REST API that returns JSON\n * @param {string} config.url The URL to request\n * @extends module:undercomplicate.BaseAdapter\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseUrlAdapter extends BaseAdapter {\n constructor(config = {}) {\n super(config);\n this._url = config.url;\n }\n\n\n /**\n * Default cache key is the URL for this request\n * @public\n */\n _getCacheKey(options) {\n return this._getURL(options);\n }\n\n /**\n * In many cases, the base url should be modified with query parameters based on request options.\n * @param options\n * @returns {*}\n * @private\n */\n _getURL(options) {\n return this._url;\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.\n // (not validating in constructor allows URL adapter to be used as more generic parent class)\n if (!this._url) {\n throw new Error('Web based resources must specify a resource URL as option \"url\"');\n }\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n });\n }\n\n _normalizeResponse(response_text, options) {\n if (typeof response_text === 'string') {\n return JSON.parse(response_text);\n }\n // Some custom usages will return other datatypes. These would need to be handled by custom normalization logic in a subclass.\n return response_text;\n }\n}\n\nexport { BaseAdapter, BaseUrlAdapter };\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value,\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import TRANSFORMS from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n // Two scenarios: we are requesting a field by full name, OR there are transforms to apply\n // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`\n const field_pattern = /^(?:\\w+:\\w+|^\\w+)(?:\\|\\w+)*$/;\n if (!field_pattern.test(field)) {\n throw new Error(`Invalid field specifier: '${field}'`);\n }\n\n const [name, ...transforms] = field.split('|');\n\n this.full_name = field; // fieldname + transforms\n this.field_name = name; // just fieldname\n this.transformations = transforms.map((name) => TRANSFORMS.get(name));\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (data[this.field_name] !== undefined) { // Fallback: value sans transforms\n val = data[this.field_name];\n } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations\n val = extra[this.field_name];\n } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {},\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable,\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * @module\n * @private\n */\nimport {getLinkedData} from './undercomplicate';\n\nimport { DATA_OPS } from '../registry';\n\n\nclass DataOperation {\n /**\n * Perform a data operation (such as a join)\n * @param {String} join_type\n * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!\n * @param params Optional user/layout parameters to be passed to the data function\n */\n constructor(join_type, initiator, params) {\n this._callable = DATA_OPS.get(join_type);\n this._initiator = initiator;\n this._params = params || [];\n }\n\n getData(plot_state, ...dependent_recordsets) {\n // Most operations are joins: they receive two pieces of data (eg left + right)\n // Other ops are possible, like consolidating just one set of records to best value per key\n // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]\n\n // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options\n const context = {plot_state, data_layer: this._initiator};\n return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));\n }\n}\n\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a\n * designated order.\n *\n * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can\n * perform its own calculations, filter results, and apply transforms without influencing other layers.\n * (while still respecting a shared cache where appropriate)\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n /**\n * Parse the data layer configuration when a layer is first created.\n * Validate config, and return entities and dependencies in a format usable for data retrieval.\n * This is used by data layers, and also other data-retrieval functions (like subscribeToDate).\n *\n * Inherent assumptions:\n * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed.\n * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be\n * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the\n * removed adapter.\n * @param {Object} namespace_options\n * @param {Array} data_operations\n * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations,\n * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate\n * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care!\n * @returns {Array} Map of entities and list of dependencies\n */\n config_to_sources(namespace_options = {}, data_operations = [], initiator) {\n const entities = new Map();\n const namespace_local_names = Object.keys(namespace_options);\n\n // 1. Specify how to coordinate data. Precedence:\n // a) EXPLICIT fetch logic,\n // b) IMPLICIT auto-generate fetch order if there is only one NS,\n // c) Throw \"spec required\" error if > 1, because 2 adapters may need to be fetched in a sequence\n let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from}\n if (!dependency_order) {\n dependency_order = { type: 'fetch', from: namespace_local_names };\n data_operations.unshift(dependency_order);\n }\n\n // Validate that all NS items are available to the root requester in DataSources. All layers recognize a\n // default value, eg people copying the examples tend to have defined a datasource called \"assoc\"\n const ns_pattern = /^\\w+$/;\n for (let [local_name, global_name] of Object.entries(namespace_options)) {\n if (!ns_pattern.test(local_name)) {\n throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`);\n }\n\n const source = this._sources.get(global_name);\n if (!source) {\n throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`);\n }\n entities.set(local_name, source);\n\n // Note: Dependency spec checker will consider \"ld(assoc)\" to match a namespace called \"ld\"\n if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) {\n // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join.\n // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch\n // Thus the default behavior is \"fetch all namespaces as though they don't depend on anything.\n // If they depend on something, only then does \"data_ops[@type=fetch].from\" need to be mutated\n dependency_order.from.push(local_name);\n }\n }\n\n let dependencies = Array.from(dependency_order.from);\n\n // Now check all joins. Are namespaces valid? Are they requesting known data?\n for (let config of data_operations) {\n let {type, name, requires, params} = config;\n if (type !== 'fetch') {\n let namecount = 0;\n if (!name) {\n name = config.name = `join${namecount}`;\n namecount += 1;\n }\n\n if (entities.has(name)) {\n throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`);\n }\n requires.forEach((require_name) => {\n if (!entities.has(require_name)) {\n throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`);\n }\n });\n\n const task = new DataOperation(type, initiator, params);\n entities.set(name, task);\n dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB)\n }\n }\n return [entities, dependencies];\n }\n\n /**\n * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end)\n * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts.\n * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances\n * (things that implement a method getData).\n * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order\n * @returns {Promise}\n */\n getData(plot_state, entities, dependencies) {\n if (!dependencies.length) {\n return Promise.resolve([]);\n }\n // The last dependency (usually the last join operation) determines the last thing returned.\n return getLinkedData(plot_state, entities, dependencies, true);\n }\n}\n\n\nexport default Requester;\n\nexport {DataOperation as _JoinTask};\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay,\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import WIDGETS from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n const options = this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n this.addWidget(layout);\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown)\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Add a new widget to the toolbar.\n * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to:\n * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here.\n * - call widget.show() to ensure that the widget is initialized and rendered correctly\n * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default.\n * @param {Object} layout The layout object describing the desired widget\n * @returns {layout.type}\n */\n addWidget(layout) {\n try {\n const widget = WIDGETS.create(layout.type, layout, this);\n this.widgets.push(widget);\n return widget;\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 14,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n const layer_legend = this.parent.data_layers[id].layout.legend;\n if (Array.isArray(layer_legend)) {\n layer_legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape === 'ribbon') {\n // Color ribbons describe a series of color stops: small boxes of color across a continuous\n // scale. Drawn horizontally, or vertically, like:\n // [red | orange | yellow | green ] label\n // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way.\n const width = +element.width || 25;\n const height = +element.height || width;\n const is_horizontal = (element.orientation || 'vertical') === 'horizontal';\n let color_stops = element.color_stops;\n\n const all_elements = selector.append('g');\n const ribbon_group = all_elements.append('g');\n const axis_group = all_elements.append('g');\n let axis_offset = 0;\n if (element.tick_labels) {\n let range;\n if (is_horizontal) {\n range = [0, width * color_stops.length - 1]; // 1 px offset to align tick with inner borders\n } else {\n range = [height * color_stops.length - 1, 0];\n }\n const scale = d3.scaleLinear()\n .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode\n .range(range);\n const axis = (is_horizontal ? d3.axisTop : d3.axisRight)(scale)\n .tickSize(3)\n .tickValues(element.tick_labels)\n .tickFormat((v) => v);\n axis_group\n .call(axis)\n .attr('class', 'lz-axis');\n let bcr = axis_group.node().getBoundingClientRect();\n axis_offset = bcr.height;\n }\n if (is_horizontal) {\n // Shift axis down (so that tick marks aren't above the origin)\n axis_group\n .attr('transform', `translate(0, ${axis_offset})`);\n // Ribbon appears below axis\n ribbon_group\n .attr('transform', `translate(0, ${axis_offset})`);\n } else {\n // Vertical mode: Shift axis ticks to the right of the ribbon\n all_elements.attr('transform', 'translate(5, 0)');\n axis_group\n .attr('transform', `translate(${width}, 0)`);\n }\n\n if (!is_horizontal) {\n // Vertical mode: renders top -> bottom but scale is usually specified low..high\n color_stops = color_stops.slice();\n color_stops.reverse();\n }\n for (let i = 0; i < color_stops.length; i++) {\n const color = color_stops[i];\n const to_next_marking = is_horizontal ? `translate(${width * i}, 0)` : `translate(0, ${height * i})`;\n ribbon_group\n .append('rect')\n .attr('class', element.class || '')\n .attr('stroke', 'black')\n .attr('transform', to_next_marking)\n .attr('stroke-width', 0.5)\n .attr('width', width)\n .attr('height', height)\n .attr('fill', color)\n .call(applyStyles, element.style || {});\n }\n\n // Note: In vertical mode, it's usually easier to put the label above the legend as a separate marker\n // This is because the legend element label is drawn last (can't use it's size to position the ribbon, which is drawn first)\n if (!is_horizontal && element.label) {\n throw new Error('Legend labels not supported for vertical ribbons (use a separate legend item as text instead)');\n }\n // This only makes sense for horizontal labels.\n label_x = (width * color_stops.length + padding);\n label_y += axis_offset;\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset] The distance between the axis title and the axis. Use this to prevent\n * the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly.\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n if (typeof layout.id !== 'string' || !layout.id) {\n throw new Error('Panel layouts must specify \"id\"');\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this._layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this._state_id = this.id;\n this.state[this._state_id] = this.state[this._state_id] || {};\n } else {\n this.state = null;\n this._state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this._data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this._data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this._zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this._event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this._event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this._data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this._data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id]._layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n const target_layer = this.data_layers[id];\n if (!target_layer) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n target_layer.destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (target_layer.svg.container) {\n target_layer.svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(target_layer._layout_idx, 1);\n delete this.state[target_layer._state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id]._layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n const { cliparea } = this.layout;\n\n // Set and position the inner border, style if necessary\n const { margin } = this.layout;\n this.inner_border\n .attr('x', margin.left)\n .attr('y', margin.top)\n .attr('width', this.parent_plot.layout.width - (margin.left + margin.right))\n .attr('height', this.layout.height - (margin.top + margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n const axes_config = this.layout.axes;\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (axes_config.x.range) {\n base_x_range.start = axes_config.x.range.start || base_x_range.start;\n base_x_range.end = axes_config.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: cliparea.height, end: 0 };\n if (axes_config.y1.range) {\n base_y1_range.start = axes_config.y1.range.start || base_y1_range.start;\n base_y1_range.end = axes_config.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: cliparea.height, end: 0 };\n if (axes_config.y2.range) {\n base_y2_range.start = axes_config.y2.range.start || base_y2_range.start;\n base_y2_range.end = axes_config.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n let { _interaction } = this.parent;\n const current_drag = _interaction.dragging;\n if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (_interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = _interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = _interaction.zooming.center - margin.left - this.layout.origin.x;\n const offset_ratio = anchor / cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (current_drag) {\n switch (current_drag.method) {\n case 'background':\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n } else {\n anchor = current_drag.start_x - margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${current_drag.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y;\n ranges[y_shifted][1] = +current_drag.dragged_y;\n } else {\n anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3);\n ranges[y_shifted][0] = cliparea.height;\n ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent._interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n // Redefine b/c might have been changed during call to parent re-render\n _interaction = this.parent._interaction;\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this._zoom_timeout !== null) {\n clearTimeout(this._zoom_timeout);\n }\n this._zoom_timeout = setTimeout(() => {\n this.parent._interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this._initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((id) => {\n const axis = this.layout.axes[id];\n if (!Object.keys(axis).length || axis.render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n axis.render = false;\n } else {\n axis.render = true;\n axis.label = axis.label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n const layout = this.layout;\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n layout.height = Math.max(Math.round(+height), layout.min_height);\n }\n }\n layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0);\n layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', layout.height);\n }\n if (this._initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n const { cliparea, margin } = this.layout;\n if (!isNaN(top) && top >= 0) {\n margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (margin.top + margin.bottom > this.layout.height) {\n extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2);\n margin.top -= extra;\n margin.bottom -= extra;\n }\n if (margin.left + margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2);\n margin.left -= extra;\n margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n margin[m] = Math.max(margin[m], 0);\n });\n cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0);\n cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0);\n cliparea.origin.x = margin.left;\n cliparea.origin.y = margin.top;\n\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this._data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent._panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n const { parent } = this;\n const y_index = this.layout.y_index;\n if (parent._panel_ids_by_y_index[y_index - 1]) {\n parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1];\n parent._panel_ids_by_y_index[y_index - 1] = this.id;\n parent.applyPanelYIndexesToPanelLayouts();\n parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n const { _panel_ids_by_y_index } = this.parent;\n if (_panel_ids_by_y_index[this.layout.y_index + 1]) {\n _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1];\n _panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this._data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this._data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this._data_promises)\n .then(() => {\n this._initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([\\w+_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future.\n * @event data_from_layer\n * @property {object} data\n * @property {String} data.layer The fully qualified ID of the layer emitting this event\n * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum\n * element. It reflects all namespaces and data operations used by that layer.\n * @see event:any_lz_event\n */\n\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this._initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this._panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this._remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this._interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this._event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this._event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this._panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this._panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id]._layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer._layer_state;\n delete this.layout.state[layer._state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n const target_panel = this.panels[id];\n if (!target_panel) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this._panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n target_panel.loader.hide();\n target_panel.toolbar.destroy(true);\n target_panel.curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (target_panel.svg.container) {\n target_panel.svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(target_panel._layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id]._layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the\n * structure of the data, instead of just displaying something.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @listens event:data_from_layer\n * @param {Object} [opts] Options\n * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched.\n * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details.\n * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested,\n * this is usually required in order to specify dependency order and join operations. See data layer documentation for details.\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(opts, success_callback) {\n const { from_layer, namespace, data_operations, onerror } = opts;\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = onerror || function (err) {\n console.error('An error occurred while acting on an external callback', err);\n };\n\n if (from_layer) {\n // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places.\n const base_prefix = `${this.getBaseId()}.`;\n // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code.\n const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`;\n\n // Ensure that a valid layer exists to watch\n let is_valid_layer = false;\n for (let p of Object.values(this.panels)) {\n is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target);\n if (is_valid_layer) {\n break;\n }\n }\n if (!is_valid_layer) {\n throw new Error(`Could not subscribe to unknown data layer ${layer_target}`);\n }\n\n const listener = (eventData) => {\n if (eventData.data.layer !== layer_target) {\n // Same event name fires for many layers; only fire success cb for the one layer we want\n return;\n }\n try {\n success_callback(eventData.data.content, this);\n } catch (error) {\n error_callback(error);\n }\n };\n\n this.on('data_from_layer', listener);\n return listener;\n }\n\n // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of\n // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized\n // in connection to some other visualization)\n if (!namespace) {\n throw new Error(\"subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option\");\n }\n\n const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot.\n const listener = () => {\n try {\n // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely,\n // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame.\n this.lzd.getData(this.state, entities, dependencies)\n .then((new_data) => success_callback(new_data, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this._remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this._remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this._remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this._initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /**\n * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another,\n * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties,\n * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.\n * @public\n */\n mutateLayout() {\n Object.values(this.panels).forEach((panel) => {\n Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout());\n });\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n const { _interaction } = this;\n if (panel_id) {\n return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(_interaction.dragging || _interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this._panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this._initialized) {\n this._panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let panel of Object.values(this.panels)) {\n if (panel.layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_layout = panel.layout;\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel_layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel_layout.margin.right, 0);\n panel_layout.width += delta;\n panel_layout.margin.left = x_linked_margins.left;\n panel_layout.margin.right = x_linked_margins.right;\n panel_layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height,\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n const resize_listener = () => this.rescaleSVG();\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener\n // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed\n if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers\n const options = { root: document.documentElement, threshold: 0.9 };\n const observer = new IntersectionObserver((entries, observer) => {\n if (entries.some((entry) => entry.intersectionRatio > 0)) {\n this.rescaleSVG();\n }\n }, options);\n // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup\n observer.observe(this.container);\n }\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this._mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this._panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent._panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent._panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this._panel_boundaries.hide_timeout);\n this._panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this._panel_boundaries.hide_timeout = setTimeout(() => {\n this._panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this._mouse_guide.vertical.attr('x', -1);\n this._mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this._mouse_guide.vertical.attr('x', coords[0]);\n this._mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n const { _interaction } = this;\n if (_interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x;\n _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y;\n this.panels[_interaction.panel_id].render();\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this._initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this._interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n const { _interaction } = this;\n if (!_interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[_interaction.panel_id] != 'object') {\n this._interaction = {};\n return this;\n }\n const panel = this.panels[_interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel._data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (_interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (_interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (_interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(_interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this._interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} value value\n */\nconst if_value = (parameters, value) => {\n if (typeof value == 'undefined' || parameters.field_value !== value) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} value value\n * @returns {*}\n */\nconst numerical_bin = (parameters, value) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+value < prev || (+value >= prev && +value < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @function ordinal_cycle\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n *\n * @function stable_choice\n *\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, value) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return nullval;\n }\n if (+value <= parameters.breaks[0]) {\n return values[0];\n } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +value && breaks[idx] >= +value) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\n/**\n * Calculate the effect direction based on beta, or the combination of beta and standard error.\n * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.\n *\n * @function effect_direction\n * @param parameters\n * @param parameters.'+' The value to return if the effect direction is positive\n * @param parameters.'-' The value to return if the effect direction is positive\n * @param parameters.beta_field The name of the field containing beta\n * @param parameters.stderr_beta_field The name of the field containing stderr_beta\n * @param {Object} input This function should receive the entire datum object, rather than one single field\n * @returns {null}\n */\nfunction effect_direction(parameters, input) {\n if (input === undefined) {\n return null;\n }\n\n const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters;\n\n if (!beta_field || !stderr_beta_field) {\n throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`);\n }\n\n const beta_val = input[beta_field];\n const se_val = input[stderr_beta_field];\n\n if (beta_val !== undefined) {\n if (se_val !== undefined) {\n if ((beta_val - 1.96 * se_val) > 0) {\n return plus_result;\n } else if ((beta_val + 1.96 * se_val) < 0) {\n return neg_result || null;\n }\n } else {\n if (beta_val > 0) {\n return plus_result;\n } else if (beta_val < 0) {\n return neg_result;\n }\n }\n }\n // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid\n // about expected data formats for layouts.\n return null;\n}\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, findFields, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n/**\n * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.\n * @property {module:LocusZoom_DataFunctions|'fetch'} type\n * @property {String[]} [from] For operations of type \"fetch\", this is required. By default, it will fill in any items provided in \"namespace\" (everything specified in namespace triggers an adapter/network request)\n * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace).\n * Eg, for ld to be fetched after association data, specify \"ld(assoc)\". Most LocusZoom examples fill in all items, in order to make the examples more clear.\n * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation.\n * Eg, if the retrieved data is combined via several left joins in series: `name: \"assoc_plus_ld\"` -> feeds into `{name: \"final\", requires: [\"assoc_plus_ld\"]}`\n * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just\n * the names of other namespaces (or data operations) whose results must be available before a join can be performed\n * @property {String[]} params Any user-defined parameters that should be passed to the particular join function\n * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']`\n * Separate from this section, data functions will also receive a copy of \"plot.state\" automatically.\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or \"line\" for a path.\n * A special \"ribbon\" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD)\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {'vertical'|'horizontal'} [orientation='vertical'] For shape \"ribbon\", specifies whether to draw the ribbon vertically or horizontally.\n * @property {Array} [tick_labels] For shape \"ribbon\", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops.\n * @property {String[]} [color_stops] For shape \"ribbon\", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels.\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n namespace: {},\n data_operations: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is\n * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely\n * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is\n * your job to assure that all of the expected fields are present in every element)\n * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data (\"assoc\")\n * to the globally unique name for something defined in LocusZoom.DataSources.\n * Namespaces allow a single layout to be reused to plot many tracks of the same type: \"given some form of association data, plot it\".\n * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse\n * a layout with a new provider of data- like plotting two association studies stacked together-\n * only the namespace section of the layout needs to be overridden.\n * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})`\n * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions})\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this._layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this._state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this._layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this._tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this._global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n\n // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval\n this._data_contract = new Set(); // List of all fields requested by the layout\n this._entities = new Map();\n this._dependencies = [];\n this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout\n }\n\n /****** Public interface: methods for manipulating the layer from other parts of LZ */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index + 1]) {\n layer_order[current_index] = layer_order[current_index + 1];\n layer_order[current_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index - 1]) {\n layer_order[current_index] = layer_order[current_index - 1];\n layer_order[current_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this._layer_state.extra_fields[id]) {\n this._layer_state.extra_fields[id] = {};\n }\n this._layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /**\n * A list of operations that should be run when the layout is mutated\n * Typically, these are things done once when a layout is first specified, that would not automatically\n * update when the layout was changed.\n * @public\n */\n mutateLayout() {\n // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract.\n if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester\n const { namespace, data_operations } = this.layout;\n this._data_contract = findFields(this.layout, Object.keys(namespace));\n const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this);\n this._entities = entities;\n this._dependencies = dependencies;\n }\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n *\n * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that\n * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data),\n * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}`\n * @protected\n * @param {Object} element The data associated with a particular element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression\n const id_field = this.layout.id_field;\n let value = element[id_field];\n if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) {\n // No field value was found directly, but if it looks like a template expression, next, try parsing that\n // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot!\n value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient\n }\n if (value === null || value === undefined) {\n // Neither exact field nor template options produced an ID\n throw new Error('Unable to generate element ID');\n }\n const element_id = value.toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Abstract method. It should be overridden by data layers that implement separate status\n * nodes, such as genes or intervals.\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or\n * a group of unrelated intervals (all markings grouped within a category).\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n\n // Does the data from the API satisfy the list of fields expected by this layout?\n // Not every record will have every possible field (example: left joins like assoc + ld). The check is \"did\n // we see this field at least once in any record at all\".\n if (this.data.length && this._data_contract.size) {\n const fields_unseen = new Set(this._data_contract);\n for (let record of this.data) {\n Object.keys(record).forEach((field) => fields_unseen.delete(field));\n if (!fields_unseen.size) {\n // Once every requested field has been seen in at least one record, no need to look at more records\n break;\n }\n }\n if (fields_unseen.size) {\n // Current implementation is a soft warning, so that certain \"incremental enhancement\" features\n // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info.\n // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data.\n console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in \"data_operations\" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`);\n }\n }\n\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value);\n }\n\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this._layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const _layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = _layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this._state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this._state_id] = _layer_state;\n }\n this._layer_state = _layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this._tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this._tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this._layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this._tooltips[id].selector.html('');\n this._tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this._tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this._tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this._tooltips[id]) {\n if (typeof this._tooltips[id].selector == 'object') {\n this._tooltips[id].selector.remove();\n }\n delete this._tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this._layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this._tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this._tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this._tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this._tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n const tooltip_layout = this.layout.tooltip;\n if (typeof tooltip_layout != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof tooltip_layout.show == 'string') {\n show_directive = { and: [ tooltip_layout.show ] };\n } else if (typeof tooltip_layout.show == 'object') {\n show_directive = tooltip_layout.show;\n }\n\n let hide_directive = {};\n if (typeof tooltip_layout.hide == 'string') {\n hide_directive = { and: [ tooltip_layout.hide ] };\n } else if (typeof tooltip_layout.hide == 'object') {\n hide_directive = tooltip_layout.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const _layer_state = this._layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (_layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this._layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this._layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true,\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this._layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this._layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self._state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies)\n .then((new_data) => {\n this.data = new_data;\n this.applyDataMethods();\n this._initialized = true;\n // Allow listeners (like subscribeToData) to see the information associated with a layer\n this.parent.emit(\n 'data_from_layer',\n { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin\n true,\n );\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 15,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n // FIXME: Make gene text font sizes scalable\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size,\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n tooltip: null,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this._global_statuses).forEach((global_status) => {\n if (this._global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n data_layer._label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this._label_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer._label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer._label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer._label_texts.nodes();\n data_layer._label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this._label_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this._label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this._label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this._label_texts) {\n this._label_texts.remove();\n }\n\n this._label_texts = this._label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this._label_lines) {\n this._label_lines.remove();\n }\n this._label_lines = this._label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this._label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this._label_texts) {\n this._label_texts.remove();\n }\n if (this._label_lines) {\n this._label_lines.remove();\n }\n if (this._label_groups) {\n this._label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this._label_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{assoc:variant|htmlescape}}
    \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
    \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
    \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
    `,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

    {{gene_name|htmlescape}}

    '\n + 'Gene ID: {{gene_id|htmlescape}}
    '\n + 'Transcript ID: {{transcript_id|htmlescape}}
    '\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
    ConstraintExpected variantsObserved variantsConst. Metric
    Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
    o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
    Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
    o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
    pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
    o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

    {{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{catalog:variant|htmlescape}}
    '\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
    '\n + 'Top Trait: {{catalog:trait|htmlescape}}
    '\n + 'Top P Value: {{catalog:log_pvalue|logtoscinotation}}
    '\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
    ' +\n '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
    ' +\n 'Promoter
    ' +\n '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
    ' +\n '{{#if access:target}}Target: {{access:target|htmlescape}}
    {{/if}}' +\n 'Score: {{access:score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n id: 'recombrate',\n namespace: { 'recomb': 'recomb' },\n data_operations: [\n { type: 'fetch', from: ['recomb'] },\n ],\n type: 'line',\n tag: 'recombination',\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: 'recomb:position',\n },\n y_axis: {\n axis: 2,\n field: 'recomb:recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)'],\n },\n {\n type: 'left_match',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'ld'],\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n id_field: 'assoc:variant',\n coalesce: {\n active: true,\n },\n point_shape: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 'diamond',\n },\n },\n {\n // Not every dataset will provide these params\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'assoc:beta',\n stderr_beta_field: 'assoc:se',\n },\n },\n 'circle',\n ],\n point_size: {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: 'ld:correlation',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8'\n {\n shape: 'ribbon',\n orientation: 'vertical',\n width: 10,\n height: 15,\n color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0],\n },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: 'assoc:position',\n },\n y_axis: {\n axis: 1,\n field: 'assoc:log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n namespace: { 'access': 'access' },\n data_operations: [\n { type: 'fetch', from: ['access'] },\n ],\n match: { send: 'access:target', receive: 'access:target' },\n // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.\n id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',\n filters: [\n { field: 'access:score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: 'access:start1',\n field2: 'access:start2',\n },\n y_axis: {\n axis: 1,\n field: 'access:score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n\n base.data_operations.push({\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_catalog',\n requires: ['assoc_plus_ld', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n });\n\n base.tooltip.html += '{{#if catalog:rsid}}
    See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n return base;\n}();\n\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n namespace: { 'phewas': 'phewas' },\n data_operations: [\n { type: 'fetch', from: ['phewas'] },\n ],\n point_shape: [\n {\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'phewas:beta',\n stderr_beta_field: 'phewas:se',\n },\n },\n 'circle',\n ],\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',\n x_axis: {\n field: 'lz_auto_x', // Automatically added by the category_scatter layer\n category_field: 'phewas:trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: 'phewas:log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: 'phewas:trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Trait: {{phewas:trait_label|htmlescape}}
    \nTrait Category: {{phewas:trait_group|htmlescape}}
    \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
    β: {{phewas:beta|scinotation|htmlescape}}
    {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}`,\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{phewas:trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: 'phewas:log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n data_operations: [\n {\n type: 'fetch',\n from: ['gene', 'constraint(gene)'],\n },\n {\n name: 'gene_constraint',\n type: 'genes_to_gnomad_constraint',\n requires: ['gene', 'constraint'],\n },\n ],\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n data_operations: [\n {\n type: 'fetch', from: ['assoc', 'catalog'],\n },\n {\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n },\n ],\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: '#0000CC',\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'catalog:rsid', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n },\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 300,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 46,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 75, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 40,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{catalog:trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: 'catalog:trait', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: 'ld:correlation', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '12px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 55, bottom: 20, left: 70 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu),\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 55, bottom: 120, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 55, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel),\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or applying namespaces.\n let base = super.get(type).get(name);\n\n // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.\n // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)\n const custom_namespaces = overrides.namespace;\n if (!base.namespace) {\n // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout\n // NOTE: The \"merge namespace\" behavior means that data layers can add new data easily, but this method\n // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).\n delete overrides.namespace;\n }\n let result = merge(overrides, base);\n\n if (custom_namespaces) {\n result = applyNamespaces(result, custom_namespaces);\n }\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n\n // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested\n // from external sources. This is purely a hint, because not every layout is generated through the registry.\n if (type === 'data_layer' && copy.namespace) {\n copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();\n }\n\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","/**\n * \"Data operation\" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results\n *\n * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation\n * is a \"join\", such as combining association + LD together into a single set of records for plotting. Several join\n * functions (that operate by analogy to SQL) are provided built-in.\n *\n * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):\n * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.\n * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,\n * this is the recommended path to do so)\n * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),\n * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,\n * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks\n * (PheWAS), automatic panel legends or color schemes (BED tracks), etc.\n *\n * Usually, a data operation receives two recordsets (the left and right members of the join, like \"assoc\" and \"ld\").\n * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network\n * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is\n * uncommon. (if possible, try to provide your data with fewer adapters/network requests!)\n *\n * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,\n * particularly for advanced features, may carry assumptions about field names/ formatting.\n * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,\n * or it may match two datasets based on a specific way of identifying the variant)\n *\n * @module LocusZoom_DataFunctions\n */\nimport {joins} from '../data/undercomplicate';\n\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"data join\" functions.\n * @alias module:LocusZoom~DataFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\nfunction _wrap_join(handle) {\n // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).\n\n // Many of our join functions are implemented with a different number of arguments than what a datafunction\n // actually receives. (eg, a join function is generic and doesn't care about \"context\" information like plot.state)\n // This wrapper is simple shared code to handle required validation and conversion stuff.\n return (context, deps, ...params) => {\n if (deps.length !== 2) {\n throw new Error('Join functions must receive exactly two recordsets');\n }\n return handle(...deps, ...params);\n };\n}\n\n// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to\n// pick the most significant claim in the catalog for a variant, rather than joining every possible match.\n// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.\nfunction assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {\n if (!assoc_data.length) {\n return assoc_data;\n }\n\n // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each\n const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);\n\n const catalog_flat = []; // Store only the top significant claim for each catalog variant entry\n for (let claims of catalog_by_variant.values()) {\n // Find max item within this set of claims, push that to catalog_\n let best = 0;\n let best_variant;\n for (let item of claims) {\n const val = item[catalog_logp_name];\n if ( val >= best) {\n best_variant = item;\n best = val;\n }\n }\n best_variant.n_catalog_matches = claims.length;\n catalog_flat.push(best_variant);\n }\n return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);\n}\n\n// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.\nfunction genes_to_gnomad_constraint(genes_data, constraint_data) {\n genes_data.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return genes_data;\n}\n\n\n/**\n * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset\n *\n * @function\n * @name left_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('left_match', _wrap_join(joins.left_match));\n\n/**\n * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.\n *\n * @function\n * @name inner_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('inner_match', _wrap_join(joins.inner_match));\n\n/**\n * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.\n *\n * @function\n * @name full_outer_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('full_outer_match', _wrap_join(joins.full_outer_match));\n\n/**\n * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.\n *\n * @function\n * @name assoc_to_gwas_catalog\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: assoc records, then catalog records\n * @param {String} assoc_key The name of the key field in association data, eg variant ID\n * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID\n * @param {String} catalog_log_p_name The name of the \"log_pvalue\" field in gwas catalog data, used to choose the most significant claim for a given variant\n */\nregistry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));\n\n/**\n * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).\n *\n * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.\n *\n * @function\n * @name genes_to_gnomad_constraint\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data\n */\nregistry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));\n\nexport default registry;\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n DATA_OPS as DataFunctions,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n WIDGETS as Widgets,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n DataFunctions,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/docs/api/LLNode.html b/docs/api/LLNode.html new file mode 100644 index 00000000..dd958dd9 --- /dev/null +++ b/docs/api/LLNode.html @@ -0,0 +1,342 @@ + + + + + JSDoc: Class: LLNode + + + + + + + + + + +
    + +

    Class: LLNode

    + + + + + + +
    + +
    + +

    + undercomplicateLLNode(key, value, metadata, prev, next)

    + +

    Implement an LRU Cache

    + + +
    + +
    +
    + + + + +

    Constructor

    + + + +

    new LLNode(key, value, metadata, prev, next)

    + + + + + + +
    +

    A single node in the linked list. Users will only need to deal with this class if using "approximate match" (cache.find())

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    key + + +string + + + + + +
    value + + +* + + + + + +
    metadata + + +object + + + + + +
    prev + + +LLNode + + + + + + null + +
    next + + +LLNode + + + + + + null + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + +
    + + + + + + + \ No newline at end of file diff --git a/docs/api/LayoutRegistry.html b/docs/api/LayoutRegistry.html index 961ef27e..ce6d26dc 100644 --- a/docs/api/LayoutRegistry.html +++ b/docs/api/LayoutRegistry.html @@ -1424,7 +1424,7 @@
    Returns:

    diff --git a/docs/api/Line.html b/docs/api/Line.html index 3ae26855..135d0080 100644 --- a/docs/api/Line.html +++ b/docs/api/Line.html @@ -719,7 +719,7 @@
    Parameters:

    diff --git a/docs/api/Panel.html b/docs/api/Panel.html index 8f7ef055..f5dd8596 100644 --- a/docs/api/Panel.html +++ b/docs/api/Panel.html @@ -4392,7 +4392,7 @@
    Returns:

    diff --git a/docs/api/Plot.html b/docs/api/Plot.html index e2f21576..4f19707b 100644 --- a/docs/api/Plot.html +++ b/docs/api/Plot.html @@ -3029,7 +3029,7 @@
    Parameters:

    diff --git a/docs/api/TransformationFunctionsRegistry.html b/docs/api/TransformationFunctionsRegistry.html index dba0482a..c2fe0b25 100644 --- a/docs/api/TransformationFunctionsRegistry.html +++ b/docs/api/TransformationFunctionsRegistry.html @@ -305,7 +305,7 @@
    Parameters:

    diff --git a/docs/api/components_data_layer_annotation_track.js.html b/docs/api/components_data_layer_annotation_track.js.html index df096c6e..6947cbb0 100644 --- a/docs/api/components_data_layer_annotation_track.js.html +++ b/docs/api/components_data_layer_annotation_track.js.html @@ -171,7 +171,7 @@

    Source: components/data_layer/annotation_track.js


    diff --git a/docs/api/components_data_layer_arcs.js.html b/docs/api/components_data_layer_arcs.js.html index 2620f2e8..c9c9db31 100644 --- a/docs/api/components_data_layer_arcs.js.html +++ b/docs/api/components_data_layer_arcs.js.html @@ -180,7 +180,7 @@

    Source: components/data_layer/arcs.js


    diff --git a/docs/api/components_data_layer_base.js.html b/docs/api/components_data_layer_base.js.html index 5e3152e5..0ac920bc 100644 --- a/docs/api/components_data_layer_base.js.html +++ b/docs/api/components_data_layer_base.js.html @@ -1377,7 +1377,7 @@

    Source: components/data_layer/base.js

    // The broadcast value can use transforms to "clean up value before sending broadcasting" 'match_requested', { value: new Field(value_to_broadcast).resolve(element), active: active }, - true + true, ); } return this; @@ -1600,7 +1600,7 @@

    Source: components/data_layer/base.js

    this.parent.emit( 'data_from_layer', { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin - true + true, ); }); } @@ -1714,7 +1714,7 @@

    Source: components/data_layer/base.js


    diff --git a/docs/api/components_data_layer_genes.js.html b/docs/api/components_data_layer_genes.js.html index 14f58ec4..4e16479c 100644 --- a/docs/api/components_data_layer_genes.js.html +++ b/docs/api/components_data_layer_genes.js.html @@ -338,7 +338,7 @@

    Source: components/data_layer/genes.js

    }) .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()) + data_layer.layout.bounding_box_padding - + data_layer.layout.label_font_size + + data_layer.layout.label_font_size, ); labels.exit() @@ -425,7 +425,7 @@

    Source: components/data_layer/genes.js


    diff --git a/docs/api/components_data_layer_highlight_regions.js.html b/docs/api/components_data_layer_highlight_regions.js.html index 8f376abb..f03d81f1 100644 --- a/docs/api/components_data_layer_highlight_regions.js.html +++ b/docs/api/components_data_layer_highlight_regions.js.html @@ -177,7 +177,7 @@

    Source: components/data_layer/highlight_regions.js


    diff --git a/docs/api/components_data_layer_line.js.html b/docs/api/components_data_layer_line.js.html index 595f63a3..5e80768c 100644 --- a/docs/api/components_data_layer_line.js.html +++ b/docs/api/components_data_layer_line.js.html @@ -303,7 +303,7 @@

    Source: components/data_layer/line.js


    diff --git a/docs/api/components_data_layer_scatter.js.html b/docs/api/components_data_layer_scatter.js.html index ead23fc6..fff43a5c 100644 --- a/docs/api/components_data_layer_scatter.js.html +++ b/docs/api/components_data_layer_scatter.js.html @@ -716,7 +716,7 @@

    Source: components/data_layer/scatter.js


    diff --git a/docs/api/components_legend.js.html b/docs/api/components_legend.js.html index b8562c8a..c410566c 100644 --- a/docs/api/components_legend.js.html +++ b/docs/api/components_legend.js.html @@ -353,7 +353,7 @@

    Source: components/legend.js


    diff --git a/docs/api/components_panel.js.html b/docs/api/components_panel.js.html index 8be9fbf7..e0d3cd4d 100644 --- a/docs/api/components_panel.js.html +++ b/docs/api/components_panel.js.html @@ -1605,7 +1605,7 @@

    Source: components/panel.js


    diff --git a/docs/api/components_plot.js.html b/docs/api/components_plot.js.html index 368ca5c1..419c495f 100644 --- a/docs/api/components_plot.js.html +++ b/docs/api/components_plot.js.html @@ -1125,7 +1125,7 @@

    Source: components/plot.js

    const panel = this.panels[panel_id]; panel.setDimensions( this.layout.width, - panel.layout.height + panel.layout.height, ); }); @@ -1543,7 +1543,7 @@

    Source: components/plot.js


    diff --git a/docs/api/components_toolbar_index.js.html b/docs/api/components_toolbar_index.js.html index a75bbe08..2bb5ea07 100644 --- a/docs/api/components_toolbar_index.js.html +++ b/docs/api/components_toolbar_index.js.html @@ -271,7 +271,7 @@

    Source: components/toolbar/index.js


    diff --git a/docs/api/components_toolbar_widgets.js.html b/docs/api/components_toolbar_widgets.js.html index d419bbcf..185e3b36 100644 --- a/docs/api/components_toolbar_widgets.js.html +++ b/docs/api/components_toolbar_widgets.js.html @@ -1671,7 +1671,7 @@

    Source: components/toolbar/widgets.js


    diff --git a/docs/api/data_adapters.js.html b/docs/api/data_adapters.js.html index afe9fd17..bfa61063 100644 --- a/docs/api/data_adapters.js.html +++ b/docs/api/data_adapters.js.html @@ -60,15 +60,10 @@

    Source: data/adapters.js

    * @module LocusZoom_Adapters */ -import {BaseUrlAdapter} from 'undercomplicate'; +import {BaseUrlAdapter} from './undercomplicate'; import {parseMarker} from '../helpers/parse'; -// NOTE: Custom adapters are annotated with `see` instead of `extend` throughout this file, to avoid clutter in the developer API docs. -// Most people using LZ data sources will never instantiate a class directly and certainly won't be calling internal -// methods, except when implementing a subclass. For most LZ users, it's usually enough to acknowledge that the -// private API methods exist in the base class. - /** * Replaced with the BaseLZAdapter class. * @public @@ -92,17 +87,20 @@

    Source: data/adapters.js

    /** - * @param {object} config - * @param [config.cache_enabled=true] - * @param [config.cache_size=3] - * @param [config.url] - * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name. - * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant) - * Typically, this is only disabled if the response payload is very unusual - * @param {String[]} [limit_fields=null] If an API returns far more data than is needed, this can be used to simplify - * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD. + * @extends module:undercomplicate.BaseUrlAdapter + * @inheritDoc */ class BaseLZAdapter extends BaseUrlAdapter { + /** + * @param [config.cache_enabled=true] + * @param [config.cache_size=3] + * @param [config.url] + * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name. + * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant) + * Typically, this is only disabled if the response payload is very unusual + * @param {String[]} [config.limit_fields=null] If an API returns far more data than is needed, this can be used to simplify + * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD. + */ constructor(config = {}) { if (config.params) { // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places. @@ -124,10 +122,11 @@

    Source: data/adapters.js

    /** * Determine how a particular request will be identified in cache. Most LZ requests are region based, - * so the default is a string concatenation of `chr_start_end` + * so the default is a string concatenation of `chr_start_end`. This adapter is "region aware"- if the user + * zooms in, it won't trigger a network request because we alread have the data needed. * @param options Receives plot.state plus any other request options defined by this source * @returns {string} - * @private + * @public */ _getCacheKey(options) { // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default @@ -153,7 +152,7 @@

    Source: data/adapters.js

    * @param records * @param options * @returns {*} - * @private + * @public */ _postProcessResponse(records, options) { if (!this._prefix_namespace || !Array.isArray(records)) { @@ -174,7 +173,7 @@

    Source: data/adapters.js

    } return acc; }, - {} + {}, ); }); } @@ -205,11 +204,13 @@

    Source: data/adapters.js

    /** * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies * of one particular web server. + * @extends module:LocusZoom_Adapters~BaseLZAdapter + * @inheritDoc */ class BaseUMAdapter extends BaseLZAdapter { /** * @param {Object} config - * @param {String} [config.build] The genome build to be used by all requests for this adapter. + * @param {String} [config.build] The genome build to be used by all requests for this adapter. (UMich APIs are all genome build aware). "GRCh37" or "GRCh38" */ constructor(config = {}) { super(config); @@ -234,7 +235,7 @@

    Source: data/adapters.js

    * @param response_text * @param options * @returns {Object[]} - * @private + * @public */ _normalizeResponse(response_text, options) { let data = super._normalizeResponse(...arguments); @@ -277,6 +278,7 @@

    Source: data/adapters.js

    * to a specific REST API. * @public * @see module:LocusZoom_Adapters~BaseUMAdapter + * @inheritDoc * * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL */ @@ -547,7 +549,7 @@

    Source: data/adapters.js

    const coord = +pos; // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby. - if ((coord && state.ldrefvar && state.chr) && (chrom !== state.chr || coord < state.start || coord > state.end)) { + if ((coord && state.ldrefvar && state.chr) && (chrom !== String(state.chr) || coord < state.start || coord > state.end)) { // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a // *copy* of plot.state, so wiping here doesn't remove the original value. state.ldrefvar = null; @@ -775,7 +777,7 @@

    Source: data/adapters.js


    diff --git a/docs/api/data_field.js.html b/docs/api/data_field.js.html index 083b0136..f371791d 100644 --- a/docs/api/data_field.js.html +++ b/docs/api/data_field.js.html @@ -99,7 +99,7 @@

    Source: data/field.js


    diff --git a/docs/api/data_requester.js.html b/docs/api/data_requester.js.html index ee7d3104..38d9208c 100644 --- a/docs/api/data_requester.js.html +++ b/docs/api/data_requester.js.html @@ -30,7 +30,7 @@

    Source: data/requester.js

    * @module * @private */ -import {getLinkedData} from 'undercomplicate'; +import {getLinkedData} from './undercomplicate'; import { DATA_OPS } from '../registry'; @@ -194,7 +194,7 @@

    Source: data/requester.js


    diff --git a/docs/api/data_sources.js.html b/docs/api/data_sources.js.html index f79bea5a..b1b827f9 100644 --- a/docs/api/data_sources.js.html +++ b/docs/api/data_sources.js.html @@ -92,7 +92,7 @@

    Source: data/sources.js


    diff --git a/docs/api/data_undercomplicate_adapter.js.html b/docs/api/data_undercomplicate_adapter.js.html new file mode 100644 index 00000000..21d3513a --- /dev/null +++ b/docs/api/data_undercomplicate_adapter.js.html @@ -0,0 +1,256 @@ + + + + + JSDoc: Source: data/undercomplicate/adapter.js + + + + + + + + + + +
    + +

    Source: data/undercomplicate/adapter.js

    + + + + + + +
    +
    +
    import {LRUCache} from './lru_cache';
    +import {clone} from './util';
    +
    +/**
    + * @param {boolean} [config.cache_enabled=true] Whether to enable the LRU cache, and store a copy of the normalized and parsed response data.
    + *  Turned on by default for most remote requests; turn off if you are using another datastore (like Vuex) or if the response body uses too much memory.
    + * @param {number} [config.cache_size=3] How many requests to cache. Track-dependent annotations like LD might benefit
    + *   from caching more items, while very large payloads (like, um, TOPMED LD) might benefit from a smaller cache size.
    + *   For most LocusZoom usages, the cache is "region aware": zooming in will use cached data, not a separate request
    + * @inheritDoc
    + * @memberOf module:undercomplicate
    + */
    +class BaseAdapter {
    +    constructor(config = {}) {
    +        this._config = config;
    +        const {
    +            // Cache control
    +            cache_enabled = true,
    +            cache_size = 3,
    +        } = config;
    +        this._enable_cache = cache_enabled;
    +        this._cache = new LRUCache(cache_size);
    +    }
    +
    +    /**
    +     * Build an object with options that control the request. This can take into account both explicit options, and prior data.
    +     * @param {Object} options Any global options passed in via `getData`. Eg, in locuszoom, every request is passed a copy of `plot.state` as the options object, in which case every adapter would expect certain basic information like `chr, start, end` to be available.
    +     * @param {Object[]} dependent_data If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, `ld(assoc)` means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.
    +     * @returns {*} An options object containing initial options, plus any calculated values relevant to the request.
    +     * @public
    +     */
    +    _buildRequestOptions(options, dependent_data) {
    +        // Perform any pre-processing required that may influence the request. Receives an array with the payloads
    +        //  for each request that preceded this one in the dependency chain
    +        // This method may optionally take dependent data into account. For many simple adapters, there won't be any dependent data!
    +        return Object.assign({}, options);
    +    }
    +
    +    /**
    +     * Determine how this request is uniquely identified in cache. Usually this is an exact match for the same key, but it doesn't have to be.
    +     * The LRU cache implements a `find` method, which means that a cache item can optionally be identified by its node
    +     * `metadata` (instead of exact key match).
    +     *  This is useful for situations where the user zooms in to a smaller region and wants the original request to
    +     *  count as a cache hit. See subclasses for example.
    +     * @param {object} options Request options from `_buildRequestOptions`
    +     * @returns {*} This is often a string concatenating unique values for a compound cache key, like `chr_start_end`. If null, it is treated as a cache miss.
    +     * @public
    +     */
    +    _getCacheKey(options) {
    +        /* istanbul ignore next */
    +        if (this._enable_cache) {
    +            throw new Error('Method not implemented');
    +        }
    +        return null;
    +    }
    +
    +    /**
    +     * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)
    +     * @param {object} options Request options from `_buildRequestOptions`
    +     * @returns {Promise}
    +     * @public
    +     */
    +    _performRequest(options) {
    +        /* istanbul ignore next */
    +        throw new Error('Not implemented');
    +    }
    +
    +    /**
    +     * Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.
    +     * @param {*} response_text The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON.
    +     * @param {Object} options Request options. These are not typically used when normalizing a response, but the object is available.
    +     * @returns {*} A list of objects, each object representing one row of data `{column_name: value_for_row}`
    +     * @public
    +     */
    +    _normalizeResponse(response_text, options) {
    +        return response_text;
    +    }
    +
    +    /**
    +     * Perform custom client-side operations on the retrieved data. For example, add calculated fields or
    +     *  perform rapid client-side filtering on cached data. Annotations are applied after cache, which means
    +     *  that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.
    +     *
    +     * This result is currently not cached, but it may become so in the future as responsibility for dynamic UI
    +     *   behavior moves to other layers of the application.
    +     * @param {Object[]} records
    +     * @param {Object} options
    +     * @returns {*}
    +     * @public
    +     */
    +    _annotateRecords(records, options) {
    +        return records;
    +    }
    +
    +    /**
    +     * A hook to transform the response after all operations are done. For example, this can be used to prefix fields
    +     *  with a namespace unique to the request, like `log_pvalue` -> `assoc:log_pvalue`. (by applying namespace prefixes to field names last,
    +     *  annotations and validation can happen on the actual API payload, without having to guess what the fields were renamed to).
    +     * @param records
    +     * @param options
    +     * @public
    +     */
    +    _postProcessResponse(records, options) {
    +        return records;
    +    }
    +
    +    /**
    +     * All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.
    +     * @param {object} options Shared options for this request. In LocusZoom, this is typically a copy of `plot.state`.
    +     * @param {Array[]} dependent_data Zero or more recordsets corresponding to each individual adapter that this one depends on.
    +     *  Can be used to build a request that takes into account prior data.
    +     * @returns {Promise<*>}
    +     */
    +    getData(options = {}, ...dependent_data) {
    +        // Public facing method to define, perform, and process the request
    +        options = this._buildRequestOptions(options, ...dependent_data);
    +
    +        const cache_key = this._getCacheKey(options);
    +
    +        // Then retrieval and parse steps: parse + normalize response, annotate
    +        let result;
    +        if (this._enable_cache && this._cache.has(cache_key)) {
    +            result = this._cache.get(cache_key);
    +        } else {
    +            // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)
    +            //  sets a special option value called `_cache_meta`, this will be used to annotate the cache entry
    +            // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,
    +            //  even if the actual cache key wasn't an exact match. (see subclasses for an example; this class is generic)
    +            result = Promise.resolve(this._performRequest(options))
    +                // Note: we cache the normalized (parsed) response
    +                .then((text) => this._normalizeResponse(text, options));
    +            this._cache.add(cache_key, result, options._cache_meta);
    +            // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request
    +            //  Otherwise, temporary failures couldn't be resolved by trying again in a moment
    +            // TODO: In the future, consider providing a way to skip requests (eg, a sentinel value to flag something
    +            //  as not cacheable, like "no dependent data means no request... but maybe in another place this is used, there will be different dependent data and a request would make sense")
    +            result.catch((e) => this._cache.remove(cache_key));
    +        }
    +
    +        return result
    +            // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache
    +            .then((data) => clone(data))
    +            .then((records) => this._annotateRecords(records, options))
    +            .then((records) => this._postProcessResponse(records, options));
    +    }
    +}
    +
    +
    +/**
    + * Fetch data over the web, usually from a REST API that returns JSON
    + * @param {string} config.url The URL to request
    + * @extends module:undercomplicate.BaseAdapter
    + * @inheritDoc
    + * @memberOf module:undercomplicate
    + */
    +class BaseUrlAdapter extends BaseAdapter {
    +    constructor(config = {}) {
    +        super(config);
    +        this._url = config.url;
    +    }
    +
    +
    +    /**
    +     * Default cache key is the URL for this request
    +     * @public
    +     */
    +    _getCacheKey(options) {
    +        return this._getURL(options);
    +    }
    +
    +    /**
    +     * In many cases, the base url should be modified with query parameters based on request options.
    +     * @param options
    +     * @returns {*}
    +     * @private
    +     */
    +    _getURL(options) {
    +        return this._url;
    +    }
    +
    +    _performRequest(options) {
    +        const url = this._getURL(options);
    +        // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.
    +        //  (not validating in constructor allows URL adapter to be used as more generic parent class)
    +        if (!this._url) {
    +            throw new Error('Web based resources must specify a resource URL as option "url"');
    +        }
    +        return fetch(url).then((response) => {
    +            if (!response.ok) {
    +                throw new Error(response.statusText);
    +            }
    +            return response.text();
    +        });
    +    }
    +
    +    _normalizeResponse(response_text, options) {
    +        if (typeof response_text === 'string') {
    +            return JSON.parse(response_text);
    +        }
    +        // Some custom usages will return other datatypes. These would need to be handled by custom normalization logic in a subclass.
    +        return response_text;
    +    }
    +}
    +
    +export { BaseAdapter, BaseUrlAdapter };
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/api/data_undercomplicate_index.js.html b/docs/api/data_undercomplicate_index.js.html new file mode 100644 index 00000000..40a674e0 --- /dev/null +++ b/docs/api/data_undercomplicate_index.js.html @@ -0,0 +1,68 @@ + + + + + JSDoc: Source: data/undercomplicate/index.js + + + + + + + + + + +
    + +

    Source: data/undercomplicate/index.js

    + + + + + + +
    +
    +
    /**
    + * The LocusZoom data retrieval library was originally created as a standalone library, mainly for LZ usage.
    + * It is inlined into the LocusZoom source code, because there are no other places it is really used, and JSDoc references are much easier
    + *  to generate with a package in the same repo.
    + *
    + * See individual adapter classes (and their documentation) for helpful guides on what methods are available, and common customizations for LocusZoom use.
    + * @see module:LocusZoom_Adapters~BaseLZAdapter
    + *
    + * @module undercomplicate
    + * @public
    + */
    +export { BaseAdapter, BaseUrlAdapter } from './adapter';
    +export {LRUCache} from './lru_cache';
    +export {getLinkedData} from './requests';
    +
    +import * as joins from './joins';
    +export {joins};
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/api/data_undercomplicate_joins.js.html b/docs/api/data_undercomplicate_joins.js.html new file mode 100644 index 00000000..0b929f81 --- /dev/null +++ b/docs/api/data_undercomplicate_joins.js.html @@ -0,0 +1,160 @@ + + + + + JSDoc: Source: data/undercomplicate/joins.js + + + + + + + + + + +
    + +

    Source: data/undercomplicate/joins.js

    + + + + + + +
    +
    +
    /**
    + * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.
    + */
    +import { clone } from './util';
    +
    +
    +/**
    + * Simple grouping function, used to identify sets of records for joining.
    + *
    + * Used internally by join helpers, exported mainly for unit testing
    + * @memberOf module:undercomplicate
    + * @param {object[]} records
    + * @param {string} group_key
    + * @returns {Map<any, any>}
    + */
    +function groupBy(records, group_key) {
    +    const result = new Map();
    +    for (let item of records) {
    +        const item_group = item[group_key];
    +
    +        if (typeof item_group === 'undefined') {
    +            throw new Error(`All records must specify a value for the field "${group_key}"`);
    +        }
    +        if (typeof item_group === 'object') {
    +            // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)
    +            throw new Error('Attempted to group on a field with non-primitive values');
    +        }
    +
    +        let group = result.get(item_group);
    +        if (!group) {
    +            group = [];
    +            result.set(item_group, group);
    +        }
    +        group.push(item);
    +    }
    +    return result;
    +}
    +
    +
    +function _any_match(type, left, right, left_key, right_key) {
    +    // Helper that consolidates logic for all three join types
    +    const right_index = groupBy(right, right_key);
    +    const results = [];
    +    for (let item of left) {
    +        const left_match_value = item[left_key];
    +        const right_matches = right_index.get(left_match_value) || [];
    +        if (right_matches.length) {
    +            // Record appears on both left and right; equiv to an inner join
    +            results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));
    +        } else if (type !== 'inner') {
    +            // Record appears on left but not right
    +            results.push(clone(item));
    +        }
    +    }
    +
    +    if (type === 'outer') {
    +        // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side
    +        const left_index = groupBy(left, left_key);
    +        for (let item of right) {
    +            const right_match_value = item[right_key];
    +            const left_matches = left_index.get(right_match_value) || [];
    +            if (!left_matches.length) {
    +                results.push(clone(item));
    +            }
    +        }
    +    }
    +    return results;
    +}
    +
    +/**
    + * Equivalent to LEFT OUTER JOIN in SQL. Return all left records, joined to matching right data where appropriate.
    + * @memberOf module:undercomplicate
    + * @param {Object[]} left The left side recordset
    + * @param {Object[]} right The right side recordset
    + * @param {string} left_key The join field in left records
    + * @param {string} right_key The join field in right records
    + * @returns {Object[]}
    + */
    +function left_match(left, right, left_key, right_key) {
    +    return _any_match('left', ...arguments);
    +}
    +
    +/**
    + * Equivalent to INNER JOIN in SQL. Only return record joins if the key field has a match on both left and right.
    + * @memberOf module:undercomplicate
    + * @param {object[]} left The left side recordset
    + * @param {object[]} right The right side recordset
    + * @param {string} left_key The join field in left records
    + * @param {string} right_key The join field in right records
    + * @returns {Object[]}
    + */
    +function inner_match(left, right, left_key, right_key) {
    +    return _any_match('inner', ...arguments);
    +}
    +
    +/**
    + * Equivalent to FULL OUTER JOIN in SQL. Return records in either recordset, joined where appropriate.
    + * @memberOf module:undercomplicate
    + * @param {object[]} left The left side recordset
    + * @param  {object[]} right The right side recordset
    + * @param {string} left_key The join field in left records
    + * @param {string} right_key The join field in right records
    + * @returns {Object[]}
    + */
    +function full_outer_match(left, right, left_key, right_key) {
    +    return _any_match('outer', ...arguments);
    +}
    +
    +export {left_match, inner_match, full_outer_match, groupBy};
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/api/data_undercomplicate_lru_cache.js.html b/docs/api/data_undercomplicate_lru_cache.js.html new file mode 100644 index 00000000..31e52c7c --- /dev/null +++ b/docs/api/data_undercomplicate_lru_cache.js.html @@ -0,0 +1,214 @@ + + + + + JSDoc: Source: data/undercomplicate/lru_cache.js + + + + + + + + + + +
    + +

    Source: data/undercomplicate/lru_cache.js

    + + + + + + +
    +
    +
    /**
    + * Implement an LRU Cache
    + */
    +
    +
    +class LLNode {
    +    /**
    +     * A single node in the linked list. Users will only need to deal with this class if using "approximate match" (`cache.find()`)
    +     * @memberOf module:undercomplicate
    +     * @param {string} key
    +     * @param {*} value
    +     * @param {object} metadata
    +     * @param {LLNode} prev
    +     * @param {LLNode} next
    +     */
    +    constructor(key, value, metadata = {}, prev = null, next = null) {
    +        this.key = key;
    +        this.value = value;
    +        this.metadata = metadata;
    +        this.prev = prev;
    +        this.next = next;
    +    }
    +}
    +
    +class LRUCache {
    +    /**
    +     * Least-recently used cache implementation, with "approximate match" semantics
    +     * @memberOf module:undercomplicate
    +     * @param {number} [max_size=3]
    +     */
    +    constructor(max_size = 3) {
    +        this._max_size = max_size;
    +        this._cur_size = 0; // replace with map.size so we aren't managing manually?
    +        this._store = new Map();
    +
    +        // Track LRU state
    +        this._head = null;
    +        this._tail = null;
    +
    +        // Validate options
    +        if (max_size === null || max_size < 0) {
    +            throw new Error('Cache "max_size" must be >= 0');
    +        }
    +    }
    +
    +    /**
    +     * Check key membership without updating LRU
    +     * @param key
    +     * @returns {boolean}
    +     */
    +    has(key) {
    +        return this._store.has(key);
    +    }
    +
    +    /**
    +     * Retrieve value from cache (if present) and update LRU cache to say an item was recently used
    +     * @param key
    +     * @returns {null|*}
    +     */
    +    get(key) {
    +        const cached = this._store.get(key);
    +        if (!cached) {
    +            return null;
    +        }
    +        if (this._head !== cached) {
    +            // Rewrite the cached value to ensure it is head of the list
    +            this.add(key, cached.value);
    +        }
    +        return cached.value;
    +    }
    +
    +    /**
    +     * Add an item. Forcibly replaces the existing cached value for the same key.
    +     * @param key
    +     * @param value
    +     * @param {Object} [metadata={}) Metadata associated with an item. Metadata can be used for lookups (`cache.find`) to test for a cache hit based on non-exact match
    +     */
    +    add(key, value, metadata = {}) {
    +        if (this._max_size === 0) {
    +            // Don't cache items if cache has 0 size.
    +            return;
    +        }
    +
    +        const prior = this._store.get(key);
    +        if (prior) {
    +            this._remove(prior);
    +        }
    +
    +        const node = new LLNode(key, value, metadata, null, this._head);
    +
    +        if (this._head) {
    +            this._head.prev = node;
    +        } else {
    +            this._tail = node;
    +        }
    +
    +        this._head = node;
    +        this._store.set(key, node);
    +
    +        if (this._max_size >= 0 && this._cur_size >= this._max_size) {
    +            const old = this._tail;
    +            this._tail = this._tail.prev;
    +            this._remove(old);
    +        }
    +        this._cur_size += 1;
    +    }
    +
    +
    +    // Cache manipulation methods
    +    clear() {
    +        this._head = null;
    +        this._tail = null;
    +        this._cur_size = 0;
    +        this._store = new Map();
    +    }
    +
    +    // Public method, remove by key
    +    remove(key) {
    +        const cached = this._store.get(key);
    +        if (!cached) {
    +            return false;
    +        }
    +        this._remove(cached);
    +        return true;
    +    }
    +
    +    // Internal implementation, useful when working on list
    +    _remove(node) {
    +        if (node.prev !== null) {
    +            node.prev.next = node.next;
    +        } else {
    +            this._head = node.next;
    +        }
    +
    +        if (node.next !== null) {
    +            node.next.prev = node.prev;
    +        } else {
    +            this._tail = node.prev;
    +        }
    +        this._store.delete(node.key);
    +        this._cur_size -= 1;
    +    }
    +
    +    /**
    +     * Find a matching item in the cache, or return null. This is useful for "approximate match" semantics,
    +     *  to check if something qualifies as a cache hit despite not having the exact same key.
    +     *  (Example: zooming into a region, where the smaller region is a subset of something already cached)
    +     * @param {function} match_callback A function to be used to test the node as a possible match (returns true or false)
    +     * @returns {null|LLNode}
    +     */
    +    find(match_callback) {
    +        let node = this._head;
    +        while (node) {
    +            const next = node.next;
    +            if (match_callback(node)) {
    +                return node;
    +            }
    +            node = next;
    +        }
    +    }
    +}
    +
    +export { LRUCache };
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/api/data_undercomplicate_requests.js.html b/docs/api/data_undercomplicate_requests.js.html new file mode 100644 index 00000000..93446489 --- /dev/null +++ b/docs/api/data_undercomplicate_requests.js.html @@ -0,0 +1,150 @@ + + + + + JSDoc: Source: data/undercomplicate/requests.js + + + + + + + + + + +
    + +

    Source: data/undercomplicate/requests.js

    + + + + + + +
    +
    +
    /**
    + * Perform a series of requests, respecting order of operations
    + * @private
    + */
    +
    +import {Sorter} from '@hapi/topo';
    +
    +
    +function _parse_declaration(spec) {
    +    // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.
    +    const parsed = /^(?<name_alone>\w+)$|((?<name_deps>\w+)+\(\s*(?<deps>[^)]+?)\s*\))/.exec(spec);
    +    if (!parsed) {
    +        throw new Error(`Unable to parse dependency specification: ${spec}`);
    +    }
    +
    +    let {name_alone, name_deps, deps} = parsed.groups;
    +    if (name_alone) {
    +        return [name_alone, []];
    +    }
    +
    +    deps = deps.split(/\s*,\s*/);
    +    return [name_deps, deps];
    +}
    +
    +/**
    + * Perform a request for data from a set of providers, taking into account dependencies between requests.
    + *  This can be a mix of network requests or other actions (like join tasks), provided that the provider be some
    + *  object that implements a method `instance.getData`
    + *
    + * Each data layer in LocusZoom will translate the internal configuration into a format used by this function.
    + *  This function is never called directly in custom user code. In locuszoom, Requester Handles You
    + *
    + * TODO Future: It would be great to add a warning if the final element in the DAG does not reference all dependencies. This is a limitation of the current toposort library we use.
    + *
    + * @param {object} shared_options Options passed globally to all requests. In LocusZoom, this is often a copy of "plot.state"
    + * @param {Map} entities A lookup of named entities that implement the method `instance.getData -> Promise`
    + * @param {String[]} dependencies A description of how to fetch entities, and what they depend on, like `['assoc', 'ld(assoc)']`.
    + *   **Order will be determined by a DAG and the last item in the DAG is all that is returned.**
    + * @param {boolean} [consolidate=true] Whether to return all results (almost never used), or just the last item in the resolved DAG.
    + *   This can be a pitfall in common usage: if you forget a "join/consolidate" task, the last result may appear to be missing some data.
    + * @returns {Promise<Object[]>}
    + */
    +function getLinkedData(shared_options, entities, dependencies, consolidate = true) {
    +    if (!dependencies.length) {
    +        return [];
    +    }
    +
    +    const parsed = dependencies.map((spec) => _parse_declaration(spec));
    +    const dag = new Map(parsed);
    +
    +    // Define the order to perform requests in, based on a DAG
    +    const toposort = new Sorter();
    +    for (let [name, deps] of dag.entries()) {
    +        try {
    +            toposort.add(name, {after: deps, group: name});
    +        } catch (e) {
    +            throw new Error(`Invalid or possible circular dependency specification for: ${name}`);
    +        }
    +    }
    +    const order = toposort.nodes;
    +
    +    // Verify that all requested entities exist by name!
    +    const responses = new Map();
    +    for (let name of order) {
    +        const provider = entities.get(name);
    +        if (!provider) {
    +            throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);
    +        }
    +
    +        // Each promise should only be triggered when the things it depends on have been resolved
    +        const depends_on = dag.get(name) || [];
    +        const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));
    +
    +        const this_result = prereq_promises.then((prior_results) => {
    +            // Each request will be told the name of the provider that requested it. This can be used during post-processing,
    +            //   eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)
    +            // This has a secondary effect: it ensures that any changes made to "shared" options in one adapter will
    +            //  not leak out to others via a mutable shared object reference.
    +            const options = Object.assign({_provider_name: name}, shared_options);
    +            return provider.getData(options, ...prior_results);
    +        });
    +        responses.set(name, this_result);
    +    }
    +    return Promise.all([...responses.values()])
    +        .then((all_results) => {
    +            if (consolidate) {
    +                // Some usages- eg fetch + data join tasks- will only require the last response in the sequence
    +                // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)
    +                return all_results[all_results.length - 1];
    +            }
    +            return all_results;
    +        });
    +}
    +
    +
    +export {getLinkedData};
    +
    +// For testing only
    +export {_parse_declaration};
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/api/data_undercomplicate_util.js.html b/docs/api/data_undercomplicate_util.js.html new file mode 100644 index 00000000..e8413ec5 --- /dev/null +++ b/docs/api/data_undercomplicate_util.js.html @@ -0,0 +1,70 @@ + + + + + JSDoc: Source: data/undercomplicate/util.js + + + + + + + + + + +
    + +

    Source: data/undercomplicate/util.js

    + + + + + + +
    +
    +
    /**
    + * @private
    + */
    +
    +import justclone from 'just-clone';
    +
    +/**
    + * The "just-clone" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.
    + * @param {object} data
    + * @returns {*}
    + */
    +function clone(data) {
    +    if (typeof data !== 'object') {
    +        return data;
    +    }
    +    return justclone(data);
    +}
    +
    +export { clone };
    +
    +
    +
    + + + + +
    + + + +
    + + + + + + + diff --git a/docs/api/ext_lz-credible-sets.js.html b/docs/api/ext_lz-credible-sets.js.html index cafaf322..1505c4a4 100644 --- a/docs/api/ext_lz-credible-sets.js.html +++ b/docs/api/ext_lz-credible-sets.js.html @@ -84,7 +84,7 @@

    Source: ext/lz-credible-sets.js

    // Set defaults. Default sig threshold is the line of GWAS significance. (as -log10p) this._config = Object.assign( { threshold: 0.95, significance_threshold: 7.301 }, - this._config + this._config, ); this._prefix_namespace = false; } @@ -410,7 +410,7 @@

    Source: ext/lz-credible-sets.js

    }, }, ], - } + }, ); return l; }(); @@ -458,7 +458,7 @@

    Source: ext/lz-credible-sets.js


    diff --git a/docs/api/ext_lz-dynamic-urls.js.html b/docs/api/ext_lz-dynamic-urls.js.html index 5d8dcbf9..9a474622 100644 --- a/docs/api/ext_lz-dynamic-urls.js.html +++ b/docs/api/ext_lz-dynamic-urls.js.html @@ -242,7 +242,7 @@

    Source: ext/lz-dynamic-urls.js


    diff --git a/docs/api/ext_lz-forest-track.js.html b/docs/api/ext_lz-forest-track.js.html index bf1ca695..41dfd54c 100644 --- a/docs/api/ext_lz-forest-track.js.html +++ b/docs/api/ext_lz-forest-track.js.html @@ -311,7 +311,7 @@

    Source: ext/lz-forest-track.js


    diff --git a/docs/api/ext_lz-intervals-enrichment.js.html b/docs/api/ext_lz-intervals-enrichment.js.html index bc620e2d..f1027c8a 100644 --- a/docs/api/ext_lz-intervals-enrichment.js.html +++ b/docs/api/ext_lz-intervals-enrichment.js.html @@ -362,7 +362,7 @@

    Source: ext/lz-intervals-enrichment.js


    diff --git a/docs/api/ext_lz-intervals-track.js.html b/docs/api/ext_lz-intervals-track.js.html index 2f85b101..b595f24d 100644 --- a/docs/api/ext_lz-intervals-track.js.html +++ b/docs/api/ext_lz-intervals-track.js.html @@ -841,7 +841,7 @@

    Source: ext/lz-intervals-track.js


    diff --git a/docs/api/ext_lz-parsers_bed.js.html b/docs/api/ext_lz-parsers_bed.js.html index 3b4f8632..7e9e6080 100644 --- a/docs/api/ext_lz-parsers_bed.js.html +++ b/docs/api/ext_lz-parsers_bed.js.html @@ -150,7 +150,7 @@

    Source: ext/lz-parsers/bed.js


    diff --git a/docs/api/ext_lz-parsers_gwas_parsers.js.html b/docs/api/ext_lz-parsers_gwas_parsers.js.html index 074b7d2e..7790ac1a 100644 --- a/docs/api/ext_lz-parsers_gwas_parsers.js.html +++ b/docs/api/ext_lz-parsers_gwas_parsers.js.html @@ -80,7 +80,7 @@

    Source: ext/lz-parsers/gwas/parsers.js

    n_samples_col, is_alt_effect = true, // whether effect allele is oriented towards alt. We don't support files like METAL, where ref/alt may switch places per line of the file delimiter = '\t', - } + }, ) { // Column IDs should be 1-indexed (human friendly) if (has(marker_col) && has(chrom_col) && has(pos_col)) { @@ -213,7 +213,7 @@

    Source: ext/lz-parsers/gwas/parsers.js


    diff --git a/docs/api/ext_lz-parsers_index.js.html b/docs/api/ext_lz-parsers_index.js.html index 5ca651d7..2459c9b7 100644 --- a/docs/api/ext_lz-parsers_index.js.html +++ b/docs/api/ext_lz-parsers_index.js.html @@ -137,7 +137,7 @@

    Source: ext/lz-parsers/index.js


    diff --git a/docs/api/ext_lz-parsers_ld.js.html b/docs/api/ext_lz-parsers_ld.js.html index f5e83b3a..ac8848c3 100644 --- a/docs/api/ext_lz-parsers_ld.js.html +++ b/docs/api/ext_lz-parsers_ld.js.html @@ -73,7 +73,7 @@

    Source: ext/lz-parsers/ld.js


    diff --git a/docs/api/ext_lz-tabix-source.js.html b/docs/api/ext_lz-tabix-source.js.html index cbddba5a..4f3a4489 100644 --- a/docs/api/ext_lz-tabix-source.js.html +++ b/docs/api/ext_lz-tabix-source.js.html @@ -169,7 +169,7 @@

    Source: ext/lz-tabix-source.js


    diff --git a/docs/api/ext_lz-widget-addons.js.html b/docs/api/ext_lz-widget-addons.js.html index 1f648975..8e0ce95f 100644 --- a/docs/api/ext_lz-widget-addons.js.html +++ b/docs/api/ext_lz-widget-addons.js.html @@ -366,7 +366,7 @@

    Source: ext/lz-widget-addons.js


    diff --git a/docs/api/global.html b/docs/api/global.html index 272d1153..4f06a34a 100644 --- a/docs/api/global.html +++ b/docs/api/global.html @@ -96,6 +96,452 @@

    +

    Methods

    + + + + + + + +

    clone(data) → {*}

    + + + + + + +
    +

    The "just-clone" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    data + + +object + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    getLinkedData(shared_options, entities, dependencies, consolidateopt) → {Promise.<Array.<Object>>}

    + + + + + + +
    +

    Perform a request for data from a set of providers, taking into account dependencies between requests. +This can be a mix of network requests or other actions (like join tasks), provided that the provider be some +object that implements a method instance.getData

    +

    Each data layer in LocusZoom will translate the internal configuration into a format used by this function. +This function is never called directly in custom user code. In locuszoom, Requester Handles You

    +

    TODO Future: It would be great to add a warning if the final element in the DAG does not reference all dependencies. This is a limitation of the current toposort library we use.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    shared_options + + +object + + + + + + + + + + + +

    Options passed globally to all requests. In LocusZoom, this is often a copy of "plot.state"

    entities + + +Map + + + + + + + + + + + +

    A lookup of named entities that implement the method instance.getData -> Promise

    dependencies + + +Array.<String> + + + + + + + + + + + +

    A description of how to fetch entities, and what they depend on, like ['assoc', 'ld(assoc)']. +Order will be determined by a DAG and the last item in the DAG is all that is returned.

    consolidate + + +boolean + + + + + + <optional>
    + + + + + +
    + + true + +

    Whether to return all results (almost never used), or just the last item in the resolved DAG. +This can be a pitfall in common usage: if you forget a "join/consolidate" task, the last result may appear to be missing some data.

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<Array.<Object>> + + +
    +
    + + + + + + + + +

    Type Definitions

    @@ -2358,7 +2804,7 @@
    Listeners of This Event:

    diff --git a/docs/api/helpers_common.js.html b/docs/api/helpers_common.js.html index e0a2516c..3317f9f4 100644 --- a/docs/api/helpers_common.js.html +++ b/docs/api/helpers_common.js.html @@ -275,7 +275,7 @@

    Source: helpers/common.js

    clearTimeout(timer); timer = setTimeout( () => func.apply(this, arguments), - delay + delay, ); }; } @@ -291,7 +291,7 @@

    Source: helpers/common.js


    diff --git a/docs/api/helpers_display.js.html b/docs/api/helpers_display.js.html index 7bd735b1..9968a5a4 100644 --- a/docs/api/helpers_display.js.html +++ b/docs/api/helpers_display.js.html @@ -394,7 +394,7 @@

    Source: helpers/display.js


    diff --git a/docs/api/helpers_jsonpath.js.html b/docs/api/helpers_jsonpath.js.html index a6ad1ab5..c642328e 100644 --- a/docs/api/helpers_jsonpath.js.html +++ b/docs/api/helpers_jsonpath.js.html @@ -264,7 +264,7 @@

    Source: helpers/jsonpath.js


    diff --git a/docs/api/helpers_layouts.js.html b/docs/api/helpers_layouts.js.html index 4cbe143f..fab9c739 100644 --- a/docs/api/helpers_layouts.js.html +++ b/docs/api/helpers_layouts.js.html @@ -228,7 +228,7 @@

    Source: helpers/layouts.js

    (acc, key) => { acc[key] = renameField(layout[key], old_name, new_name, warn_transforms); return acc; - }, {} + }, {}, ); } else if (this_type !== 'string') { // Field names are always strings. If the value isn't a string, don't even try to change it. @@ -268,7 +268,7 @@

    Source: helpers/layouts.js

    return mutate( layout, selector, - value_or_callable + value_or_callable, ); } @@ -295,7 +295,7 @@

    Source: helpers/layouts.js


    diff --git a/docs/api/helpers_render.js.html b/docs/api/helpers_render.js.html index 74b8235a..a2df5e42 100644 --- a/docs/api/helpers_render.js.html +++ b/docs/api/helpers_render.js.html @@ -121,7 +121,7 @@

    Source: helpers/render.js


    diff --git a/docs/api/helpers_scalable.js.html b/docs/api/helpers_scalable.js.html index 782a628b..a5f94514 100644 --- a/docs/api/helpers_scalable.js.html +++ b/docs/api/helpers_scalable.js.html @@ -293,7 +293,7 @@

    Source: helpers/scalable.js


    diff --git a/docs/api/helpers_transforms.js.html b/docs/api/helpers_transforms.js.html index def5b366..1a03ef29 100644 --- a/docs/api/helpers_transforms.js.html +++ b/docs/api/helpers_transforms.js.html @@ -184,7 +184,7 @@

    Source: helpers/transforms.js


    diff --git a/docs/api/index.html b/docs/api/index.html index d5c1b3a5..50e5e5c8 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -45,14 +45,16 @@

    LocusZoom

    LocusZoom is a Javascript/d3 embeddable plugin for interactively visualizing statistical genetic data from customizable sources.

    +

    For more information, see our paper:

    +

    Boughton, A. P. et al. LocusZoom.js: interactive and embeddable visualization of genetic association study results. Bioinformatics (2021) doi:10.1093/bioinformatics/btab186.

    This is a low level library aimed at developers who want to customize their own data sharing/visualization tools. If you are a genetics researcher who just wants to make a fast visualization of your research results, try our user-friendly plot-your-own data services built on LocusZoom.js: my.locuszoom.org and LocalZoom.

    Build Status

    See https://statgen.github.io/locuszoom/docs/ for full documentation and API reference.

    To see functional examples of plots generated with LocusZoom.js see statgen.github.io/locuszoom and statgen.github.io/locuszoom/#examples.

    LocusZoom.js Standard Association Plot

    -

    Making a LocusZoom Plot

    +

    Making a LocusZoom Plot: Quickstart tutorial

    1. Include Necessary JavaScript and CSS

    -

    The page you build that embeds the LocusZoom plugin must include the following resources, found in the dist directory (or via CDN):

    +

    The page you build that embeds the LocusZoom plugin must include the following resources, found in the dist directory (or preferably loaded via CDN):

    • d3.js
      @@ -171,11 +173,11 @@

      Modify a Predefined Layout

      LocusZoom.Layouts.get("data_layer", "genes", overrides);

    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:

    +

    State is JSON-serializable object containing information that can affect the entire plot (including all data retrieval requests). State can be set before or after the plot is initialized. For example, the following special-named fields will cause the plot to be loaded to a specific region of interest on first render:

    const layout = LocusZoom.Layouts.get('plot', 'standard_association', { state: { chr: 6, start: 20379709, end: 20979709 } })
     
    -

    Predefining State With data-region

    -

    You can also describe the locus query aspect of the State (chromosome, start, and end position) using a data-region attribute of the containing element before populating it, like so:

    +

    Alternate: setting the initial view via data-region

    +

    You can also describe the locususing a data-region attribute of the containing element before populating it, like so:

    <div id="lz-plot" data-region="10:114550452-115067678"></div>
     

    When LocusZoom.populate() is executed on the element defined above it will automatically parse any data-region parameter to convert those values into the initial state.

    @@ -197,8 +199,8 @@

    Other supported build commands:

  • npm run test - Run unit tests (optional: npm run test:coverage to output a code coverage report)
  • npm run dev - Automatically rebuild the library whenever code changes (development mode)
  • npm run build - Run tests, and if they pass, build the library for release
  • -
  • npm run css - Rebuild the CSS using SASS
  • -
  • npm run docs - Build the library documentation
  • +
  • npm run css - Rebuild the CSS using SASS (CSS rarely changes, so this doesn't get done automatically in dev mode)
  • +
  • npm run docs - Build just the library documentation
  • Automated Testing

    LocusZoom uses Mocha for unit testing. Tests are located in the test subdirectory. Use npm run test.

    @@ -219,7 +221,7 @@

    Help and Support


    diff --git a/docs/api/index.js.html b/docs/api/index.js.html index ff36b2e6..0b3e4b02 100644 --- a/docs/api/index.js.html +++ b/docs/api/index.js.html @@ -118,7 +118,7 @@

    Source: index.js


    diff --git a/docs/api/layouts_index.js.html b/docs/api/layouts_index.js.html index f1fc8e0d..1aa57924 100644 --- a/docs/api/layouts_index.js.html +++ b/docs/api/layouts_index.js.html @@ -750,7 +750,7 @@

    Source: layouts/index.js

    button_html: '<<', position: 'right', group_position: 'start', - } + }, ); return base; }(); @@ -939,7 +939,7 @@

    Source: layouts/index.js

    position: 'right', button_html: 'Resize', }, - deepCopy(gene_selector_menu) + deepCopy(gene_selector_menu), ); return base; })(), @@ -1095,7 +1095,7 @@

    Source: layouts/index.js

    // This is a companion to the "match" directive in the coaccessibility panel const base = Object.assign( { height: 270 }, - deepCopy(genes_panel) + deepCopy(genes_panel), ); const layer = base.data_layers[0]; layer.match = { send: 'gene_name', receive: 'gene_name' }; @@ -1183,7 +1183,7 @@

    Source: layouts/index.js


    diff --git a/docs/api/module-LocusZoom-DataSources.html b/docs/api/module-LocusZoom-DataSources.html index 2109126d..cd58e5c7 100644 --- a/docs/api/module-LocusZoom-DataSources.html +++ b/docs/api/module-LocusZoom-DataSources.html @@ -1094,7 +1094,7 @@
    Returns:

    diff --git a/docs/api/module-LocusZoom.html b/docs/api/module-LocusZoom.html index cd27a409..f45cd810 100644 --- a/docs/api/module-LocusZoom.html +++ b/docs/api/module-LocusZoom.html @@ -1192,7 +1192,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Adapters-AssociationLZ.html b/docs/api/module-LocusZoom_Adapters-AssociationLZ.html index 1cebe25c..79600334 100644 --- a/docs/api/module-LocusZoom_Adapters-AssociationLZ.html +++ b/docs/api/module-LocusZoom_Adapters-AssociationLZ.html @@ -144,7 +144,7 @@
    Parameters:
    Source:
    @@ -213,7 +213,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Adapters-BaseAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseAdapter.html index 67414ecc..86c77af3 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseAdapter.html @@ -96,7 +96,7 @@

    new BaseAd
    Source:
    @@ -158,7 +158,7 @@

    new BaseAd
    diff --git a/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html index 9c0708e2..93cdb0de 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html @@ -146,7 +146,7 @@

    Parameters:
    Source:
    @@ -219,7 +219,7 @@

    Extends


    diff --git a/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html index ccd7a65e..d4ed802d 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html @@ -29,7 +29,7 @@

    Class: BaseLZAdapter

    - LocusZoom_Adapters~BaseLZAdapter(config, limit_fieldsopt)

    + LocusZoom_Adapters~BaseLZAdapter()

    @@ -42,7 +42,7 @@

    -

    new BaseLZAdapter(config, limit_fieldsopt)

    +

    new BaseLZAdapter()

    @@ -86,65 +86,7 @@
    Parameters:
    - config - - - - - -object - - - - - - - - - - - - - - - - - - - - - - -
    Properties
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + - -
    NameTypeAttributesDefaultDescription
    cache_enabledconfig.cache_enabled @@ -178,7 +120,7 @@
    Properties
    cache_sizeconfig.cache_size @@ -212,7 +154,7 @@
    Properties
    urlconfig.url @@ -244,7 +186,7 @@
    Properties
    prefix_namespaceconfig.prefix_namespace @@ -277,17 +219,10 @@
    Properties
    - - - - - - limit_fields + config.limit_fields @@ -361,7 +296,7 @@
    Properties
    Source:
    @@ -395,6 +330,17 @@
    Properties
    +

    Extends

    + + + + + + + + @@ -417,7 +363,7 @@

    Methods

    -

    _findPrefixedKey(a_record, fieldname)

    +

    _annotateRecords(records, options) → {*}

    @@ -425,11 +371,11 @@

    _find
    -

    Convenience method, manually called in LZ sources that deal with dependent data.

    -

    In the last step of fetching data, LZ adds a prefix to each field name. -This means that operations like "build query based on prior data" can't just ask for "log_pvalue" because -they are receiving "assoc:log_pvalue" or some such unknown prefix.

    -

    This helper lets us use dependent data more easily. Not every adapter needs to use this method.

    +

    Perform custom client-side operations on the retrieved data. For example, add calculated fields or +perform rapid client-side filtering on cached data. Annotations are applied after cache, which means +that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.

    +

    This result is currently not cached, but it may become so in the future as responsibility for dynamic UI +behavior moves to other layers of the application.

    @@ -465,7 +411,30 @@

    Parameters:
    - a_record + records + + + + + +Array.<Object> + + + + + + + + + + + + + + + + + options @@ -481,20 +450,180 @@
    Parameters:
    -

    One record (often the first one in a set of records)

    + + + + + + + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _buildRequestOptions(options, dependent_data) → {*}

    + + + + + + +
    +

    Build an object with options that control the request. This can take into account both explicit options, and prior data.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + @@ -525,6 +654,11 @@
    Parameters:
    +
    Overrides:
    +
    + @@ -545,7 +679,7 @@
    Parameters:
    Source:
    @@ -570,6 +704,1075 @@
    Parameters:
    +
    Returns:
    + + +
    +

    An options object containing initial options, plus any calculated values relevant to the request.

    +
    + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _findPrefixedKey(a_record, fieldname)

    + + + + + + +
    +

    Convenience method, manually called in LZ sources that deal with dependent data.

    +

    In the last step of fetching data, LZ adds a prefix to each field name. +This means that operations like "build query based on prior data" can't just ask for "log_pvalue" because +they are receiving "assoc:log_pvalue" or some such unknown prefix.

    +

    This helper lets us use dependent data more easily. Not every adapter needs to use this method.

    +
    + + + + + + + + + +
    Parameters:
    + + +
    NameTypeDescription
    options + + +Object + + + +

    Any global options passed in via getData. Eg, in locuszoom, every request is passed a copy of plot.state as the options object, in which case every adapter would expect certain basic information like chr, start, end to be available.

    fieldnamedependent_data -String +Array.<Object> @@ -504,7 +633,7 @@
    Parameters:
    -

    The desired fieldname, eg "log_pvalue"

    If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, ld(assoc) means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    a_record + + +Object + + + +

    One record (often the first one in a set of records)

    fieldname + + +String + + + +

    The desired fieldname, eg "log_pvalue"

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    _getCacheKey(options) → {string}

    + + + + + + +
    +

    Determine how a particular request will be identified in cache. Most LZ requests are region based, +so the default is a string concatenation of chr_start_end. This adapter is "region aware"- if the user +zooms in, it won't trigger a network request because we alread have the data needed.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + +

    Receives plot.state plus any other request options defined by this source

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +string + + +
    +
    + + + + + + + + + + + + + +

    _normalizeResponse(response_text, options) → {*}

    + + + + + + +
    +

    Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    response_text + + +* + + + +

    The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON.

    options + + +Object + + + +

    Request options. These are not typically used when normalizing a response, but the object is available.

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    A list of objects, each object representing one row of data {column_name: value_for_row}

    +
    + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _performRequest(options) → {Promise}

    + + + + + + +
    +

    Perform the act of data retrieval (eg from a URL, blob, or JSON entity)

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +object + + + +

    Request options from _buildRequestOptions

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise + + +
    +
    + + + + + + + + + + + + + +

    _postProcessResponse(records, options) → {*}

    + + + + + + +
    +

    Add the "local namespace" as a prefix for every field returned for this request. Eg if the association api +returns a field called variant, and the source is referred to as "assoc" within a particular data layer, then +the returned records will have a field called "assoc:variant"

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    records + +
    options + +
    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    getData(options, …dependent_data) → {Promise.<*>}

    + + + + + + +
    +

    All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDescription
    options + + +object + + + + + + + + + +

    Shared options for this request. In LocusZoom, this is typically a copy of plot.state.

    dependent_data + + +Array.<Array> + + + + + + + + + + <repeatable>
    + +

    Zero or more recordsets corresponding to each individual adapter that this one depends on. +Can be used to build a request that takes into account prior data.

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<*> + + +
    +
    + + @@ -591,7 +1794,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html index 89797389..15bba9cb 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html @@ -157,7 +157,7 @@
    Properties
    -

    The genome build to be used by all requests for this adapter.

    +

    The genome build to be used by all requests for this adapter. (UMich APIs are all genome build aware). "GRCh37" or "GRCh38"

    @@ -205,7 +205,7 @@
    Properties
    Source:
    @@ -239,19 +239,1446 @@
    Properties
    +

    Extends

    + + + + + + + + + + + + + + + + + + + + + + +

    Methods

    + + + + + + + +

    _annotateRecords(records, options) → {*}

    + + + + + + +
    +

    Perform custom client-side operations on the retrieved data. For example, add calculated fields or +perform rapid client-side filtering on cached data. Annotations are applied after cache, which means +that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.

    +

    This result is currently not cached, but it may become so in the future as responsibility for dynamic UI +behavior moves to other layers of the application.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    records + + +Array.<Object> + + + +
    options + + +Object + + + +
    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _buildRequestOptions(options, dependent_data) → {*}

    + + + + + + +
    +

    Build an object with options that control the request. This can take into account both explicit options, and prior data.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +Object + + + +

    Any global options passed in via getData. Eg, in locuszoom, every request is passed a copy of plot.state as the options object, in which case every adapter would expect certain basic information like chr, start, end to be available.

    dependent_data + + +Array.<Object> + + + +

    If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, ld(assoc) means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    An options object containing initial options, plus any calculated values relevant to the request.

    +
    + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _findPrefixedKey(a_record, fieldname)

    + + + + + + +
    +

    Convenience method, manually called in LZ sources that deal with dependent data.

    +

    In the last step of fetching data, LZ adds a prefix to each field name. +This means that operations like "build query based on prior data" can't just ask for "log_pvalue" because +they are receiving "assoc:log_pvalue" or some such unknown prefix.

    +

    This helper lets us use dependent data more easily. Not every adapter needs to use this method.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    a_record + + +Object + + + +

    One record (often the first one in a set of records)

    fieldname + + +String + + + +

    The desired fieldname, eg "log_pvalue"

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    _getCacheKey(options) → {string}

    + + + + + + +
    +

    Determine how a particular request will be identified in cache. Most LZ requests are region based, +so the default is a string concatenation of chr_start_end. This adapter is "region aware"- if the user +zooms in, it won't trigger a network request because we alread have the data needed.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + +

    Receives plot.state plus any other request options defined by this source

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +string + + +
    +
    + + + + + + + + + + + + + +

    _normalizeResponse(response_text, options) → {Array.<Object>}

    + + + + + + +
    +

    Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    response_text + +
    options + +
    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + + + + + + + + + + +

    _performRequest(options) → {Promise}

    + + + + + + +
    +

    Perform the act of data retrieval (eg from a URL, blob, or JSON entity)

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +object + + + +

    Request options from _buildRequestOptions

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise + + +
    +
    + + + + + + + + + + + + + +

    _postProcessResponse(records, options) → {*}

    + + + + + + +
    +

    Add the "local namespace" as a prefix for every field returned for this request. Eg if the association api +returns a field called variant, and the source is referred to as "assoc" within a particular data layer, then +the returned records will have a field called "assoc:variant"

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    records + +
    options + +
    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    getData(options, …dependent_data) → {Promise.<*>}

    + + + + + + +
    +

    All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDescription
    options + + +object + + + + + + + + + +

    Shared options for this request. In LocusZoom, this is typically a copy of plot.state.

    dependent_data + + +Array.<Array> + + + + + + + + + + <repeatable>
    + +

    Zero or more recordsets corresponding to each individual adapter that this one depends on. +Can be used to build a request that takes into account prior data.

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + +
    Source:
    +
    + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<*> + + +
    +
    + + + + + @@ -267,7 +1694,7 @@
    Properties

    diff --git a/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html b/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html index c5965f25..d9ae7bd1 100644 --- a/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html +++ b/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html @@ -277,7 +277,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html b/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html index 17fc62e0..1007a9ed 100644 --- a/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html +++ b/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html @@ -185,7 +185,7 @@
    Parameters:
    Source:
    @@ -300,7 +300,7 @@

    _no
    Source:
    @@ -346,7 +346,7 @@

    _no
    diff --git a/docs/api/module-LocusZoom_Adapters-GeneLZ.html b/docs/api/module-LocusZoom_Adapters-GeneLZ.html index f1b965a0..cc74b00f 100644 --- a/docs/api/module-LocusZoom_Adapters-GeneLZ.html +++ b/docs/api/module-LocusZoom_Adapters-GeneLZ.html @@ -216,7 +216,7 @@

    Parameters:
    Source:
    @@ -331,7 +331,7 @@

    _getURLSource:
    @@ -377,7 +377,7 @@

    _getURL
    diff --git a/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html b/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html index 0249e9a1..f2fc1f42 100644 --- a/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html +++ b/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html @@ -222,7 +222,7 @@
    Parameters:
    Source:
    @@ -337,7 +337,7 @@

    _getURLSource:
    @@ -383,7 +383,7 @@

    _getURL
    diff --git a/docs/api/module-LocusZoom_Adapters-IntervalLZ.html b/docs/api/module-LocusZoom_Adapters-IntervalLZ.html index 96b34d61..58f7d838 100644 --- a/docs/api/module-LocusZoom_Adapters-IntervalLZ.html +++ b/docs/api/module-LocusZoom_Adapters-IntervalLZ.html @@ -214,7 +214,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Adapters-LDServer.html b/docs/api/module-LocusZoom_Adapters-LDServer.html index 6d6a469a..06bce955 100644 --- a/docs/api/module-LocusZoom_Adapters-LDServer.html +++ b/docs/api/module-LocusZoom_Adapters-LDServer.html @@ -308,7 +308,7 @@
    Parameters:
    Source:
    @@ -377,7 +377,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Adapters-PheWASLZ.html b/docs/api/module-LocusZoom_Adapters-PheWASLZ.html index 060a7d2c..00a12c4d 100644 --- a/docs/api/module-LocusZoom_Adapters-PheWASLZ.html +++ b/docs/api/module-LocusZoom_Adapters-PheWASLZ.html @@ -167,7 +167,7 @@
    Parameters:
    Source:
    @@ -236,7 +236,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Adapters-RecombLZ.html b/docs/api/module-LocusZoom_Adapters-RecombLZ.html index 533587c1..6c313970 100644 --- a/docs/api/module-LocusZoom_Adapters-RecombLZ.html +++ b/docs/api/module-LocusZoom_Adapters-RecombLZ.html @@ -216,7 +216,7 @@
    Parameters:
    Source:
    @@ -331,7 +331,7 @@

    _getURLSource:
    @@ -377,7 +377,7 @@

    _getURL
    diff --git a/docs/api/module-LocusZoom_Adapters-StaticSource.html b/docs/api/module-LocusZoom_Adapters-StaticSource.html index 82e6c57e..bc9826dc 100644 --- a/docs/api/module-LocusZoom_Adapters-StaticSource.html +++ b/docs/api/module-LocusZoom_Adapters-StaticSource.html @@ -149,7 +149,7 @@
    Parameters:
    Source:
    @@ -218,7 +218,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html b/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html index 82296ff5..236aaa75 100644 --- a/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html +++ b/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html @@ -386,7 +386,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Adapters-UserTabixLD.html b/docs/api/module-LocusZoom_Adapters-UserTabixLD.html index 532627f0..423ac710 100644 --- a/docs/api/module-LocusZoom_Adapters-UserTabixLD.html +++ b/docs/api/module-LocusZoom_Adapters-UserTabixLD.html @@ -186,7 +186,7 @@

    Extends


    diff --git a/docs/api/module-LocusZoom_Adapters.html b/docs/api/module-LocusZoom_Adapters.html index 7b22028a..16394336 100644 --- a/docs/api/module-LocusZoom_Adapters.html +++ b/docs/api/module-LocusZoom_Adapters.html @@ -365,7 +365,7 @@

    (inner) St
    diff --git a/docs/api/module-LocusZoom_DataFunctions.html b/docs/api/module-LocusZoom_DataFunctions.html index 320fab38..cb2d4677 100644 --- a/docs/api/module-LocusZoom_DataFunctions.html +++ b/docs/api/module-LocusZoom_DataFunctions.html @@ -1117,7 +1117,7 @@

    Parameters:

    diff --git a/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html b/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html index fe9c33f1..8bdb7cff 100644 --- a/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html +++ b/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html @@ -4189,7 +4189,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_DataLayers-annotation_track.html b/docs/api/module-LocusZoom_DataLayers-annotation_track.html index 2a577289..78d66d4d 100644 --- a/docs/api/module-LocusZoom_DataLayers-annotation_track.html +++ b/docs/api/module-LocusZoom_DataLayers-annotation_track.html @@ -391,7 +391,7 @@

    (static, cons
    diff --git a/docs/api/module-LocusZoom_DataLayers-arcs.html b/docs/api/module-LocusZoom_DataLayers-arcs.html index 6802d2ad..717aa76f 100644 --- a/docs/api/module-LocusZoom_DataLayers-arcs.html +++ b/docs/api/module-LocusZoom_DataLayers-arcs.html @@ -622,7 +622,7 @@

    (static, cons
    diff --git a/docs/api/module-LocusZoom_DataLayers-category_forest.html b/docs/api/module-LocusZoom_DataLayers-category_forest.html index 19193239..b2a2ca5a 100644 --- a/docs/api/module-LocusZoom_DataLayers-category_forest.html +++ b/docs/api/module-LocusZoom_DataLayers-category_forest.html @@ -167,7 +167,7 @@

    new ca
    diff --git a/docs/api/module-LocusZoom_DataLayers-category_scatter.html b/docs/api/module-LocusZoom_DataLayers-category_scatter.html index 8e42c512..b41ef571 100644 --- a/docs/api/module-LocusZoom_DataLayers-category_scatter.html +++ b/docs/api/module-LocusZoom_DataLayers-category_scatter.html @@ -475,7 +475,7 @@

    Returns:

    diff --git a/docs/api/module-LocusZoom_DataLayers-forest.html b/docs/api/module-LocusZoom_DataLayers-forest.html index aa733bde..9878f952 100644 --- a/docs/api/module-LocusZoom_DataLayers-forest.html +++ b/docs/api/module-LocusZoom_DataLayers-forest.html @@ -598,7 +598,7 @@
    Fires:

    diff --git a/docs/api/module-LocusZoom_DataLayers-genes.html b/docs/api/module-LocusZoom_DataLayers-genes.html index 327934df..86aa0ab6 100644 --- a/docs/api/module-LocusZoom_DataLayers-genes.html +++ b/docs/api/module-LocusZoom_DataLayers-genes.html @@ -1224,7 +1224,7 @@

    render
    diff --git a/docs/api/module-LocusZoom_DataLayers-highlight_regions.html b/docs/api/module-LocusZoom_DataLayers-highlight_regions.html index d1525458..c8a3d19f 100644 --- a/docs/api/module-LocusZoom_DataLayers-highlight_regions.html +++ b/docs/api/module-LocusZoom_DataLayers-highlight_regions.html @@ -542,7 +542,7 @@

    (static, cons
    diff --git a/docs/api/module-LocusZoom_DataLayers-intervals.html b/docs/api/module-LocusZoom_DataLayers-intervals.html index 4a88586b..fec1aa23 100644 --- a/docs/api/module-LocusZoom_DataLayers-intervals.html +++ b/docs/api/module-LocusZoom_DataLayers-intervals.html @@ -964,7 +964,7 @@
    Returns:

    diff --git a/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html b/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html index f73e4af9..e495fa1f 100644 --- a/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html +++ b/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html @@ -575,7 +575,7 @@

    (static, cons
    diff --git a/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html b/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html index 01d783b1..f557c2a3 100644 --- a/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html +++ b/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html @@ -665,7 +665,7 @@

    render
    diff --git a/docs/api/module-LocusZoom_DataLayers-scatter.html b/docs/api/module-LocusZoom_DataLayers-scatter.html index 76f7d137..7c137ce0 100644 --- a/docs/api/module-LocusZoom_DataLayers-scatter.html +++ b/docs/api/module-LocusZoom_DataLayers-scatter.html @@ -1218,7 +1218,7 @@
    Properties:

    diff --git a/docs/api/module-LocusZoom_DataLayers.html b/docs/api/module-LocusZoom_DataLayers.html index 9d8ce949..dc24f594 100644 --- a/docs/api/module-LocusZoom_DataLayers.html +++ b/docs/api/module-LocusZoom_DataLayers.html @@ -1573,7 +1573,7 @@
    Properties:

    diff --git a/docs/api/module-LocusZoom_Layouts.html b/docs/api/module-LocusZoom_Layouts.html index a880a2cd..290a8d08 100644 --- a/docs/api/module-LocusZoom_Layouts.html +++ b/docs/api/module-LocusZoom_Layouts.html @@ -3319,7 +3319,7 @@
    Type:

    diff --git a/docs/api/module-LocusZoom_MatchFunctions.html b/docs/api/module-LocusZoom_MatchFunctions.html index 99ea6d05..12a94623 100644 --- a/docs/api/module-LocusZoom_MatchFunctions.html +++ b/docs/api/module-LocusZoom_MatchFunctions.html @@ -1532,7 +1532,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_ScaleFunctions.html b/docs/api/module-LocusZoom_ScaleFunctions.html index 54d8952f..5384ba88 100644 --- a/docs/api/module-LocusZoom_ScaleFunctions.html +++ b/docs/api/module-LocusZoom_ScaleFunctions.html @@ -1958,7 +1958,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_TransformationFunctions.html b/docs/api/module-LocusZoom_TransformationFunctions.html index 6efaa591..38effa68 100644 --- a/docs/api/module-LocusZoom_TransformationFunctions.html +++ b/docs/api/module-LocusZoom_TransformationFunctions.html @@ -1257,7 +1257,7 @@
    Returns:

    diff --git a/docs/api/module-LocusZoom_Widgets-BaseWidget.html b/docs/api/module-LocusZoom_Widgets-BaseWidget.html index df0486a4..f9db92ca 100644 --- a/docs/api/module-LocusZoom_Widgets-BaseWidget.html +++ b/docs/api/module-LocusZoom_Widgets-BaseWidget.html @@ -1654,7 +1654,7 @@

    update
    diff --git a/docs/api/module-LocusZoom_Widgets-_Button.html b/docs/api/module-LocusZoom_Widgets-_Button.html index 64fc2c28..511ac229 100644 --- a/docs/api/module-LocusZoom_Widgets-_Button.html +++ b/docs/api/module-LocusZoom_Widgets-_Button.html @@ -3461,7 +3461,7 @@
    Returns:

    diff --git a/docs/api/module-LocusZoom_Widgets-display_options.html b/docs/api/module-LocusZoom_Widgets-display_options.html index 6976e87d..d7d2c17d 100644 --- a/docs/api/module-LocusZoom_Widgets-display_options.html +++ b/docs/api/module-LocusZoom_Widgets-display_options.html @@ -467,7 +467,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Widgets-download_png.html b/docs/api/module-LocusZoom_Widgets-download_png.html index 3f131348..a9b059c6 100644 --- a/docs/api/module-LocusZoom_Widgets-download_png.html +++ b/docs/api/module-LocusZoom_Widgets-download_png.html @@ -370,7 +370,7 @@

    Extends


    diff --git a/docs/api/module-LocusZoom_Widgets-download_svg.html b/docs/api/module-LocusZoom_Widgets-download_svg.html index 09ac6f50..e014b60f 100644 --- a/docs/api/module-LocusZoom_Widgets-download_svg.html +++ b/docs/api/module-LocusZoom_Widgets-download_svg.html @@ -459,7 +459,7 @@
    Returns:

    diff --git a/docs/api/module-LocusZoom_Widgets-filter_field.html b/docs/api/module-LocusZoom_Widgets-filter_field.html index 0535635f..ba27baf9 100644 --- a/docs/api/module-LocusZoom_Widgets-filter_field.html +++ b/docs/api/module-LocusZoom_Widgets-filter_field.html @@ -668,7 +668,7 @@
    Fires:

    diff --git a/docs/api/module-LocusZoom_Widgets-menu.html b/docs/api/module-LocusZoom_Widgets-menu.html index 90202e9c..b13718d1 100644 --- a/docs/api/module-LocusZoom_Widgets-menu.html +++ b/docs/api/module-LocusZoom_Widgets-menu.html @@ -253,7 +253,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Widgets-move_panel_down.html b/docs/api/module-LocusZoom_Widgets-move_panel_down.html index a210ad22..a9e2d08c 100644 --- a/docs/api/module-LocusZoom_Widgets-move_panel_down.html +++ b/docs/api/module-LocusZoom_Widgets-move_panel_down.html @@ -164,7 +164,7 @@

    new mo
    diff --git a/docs/api/module-LocusZoom_Widgets-move_panel_up.html b/docs/api/module-LocusZoom_Widgets-move_panel_up.html index 0c4211a2..55de4ce5 100644 --- a/docs/api/module-LocusZoom_Widgets-move_panel_up.html +++ b/docs/api/module-LocusZoom_Widgets-move_panel_up.html @@ -164,7 +164,7 @@

    new move
    diff --git a/docs/api/module-LocusZoom_Widgets-region_scale.html b/docs/api/module-LocusZoom_Widgets-region_scale.html index 6df9a5a2..15fe3e8f 100644 --- a/docs/api/module-LocusZoom_Widgets-region_scale.html +++ b/docs/api/module-LocusZoom_Widgets-region_scale.html @@ -167,7 +167,7 @@

    new regio
    diff --git a/docs/api/module-LocusZoom_Widgets-remove_panel.html b/docs/api/module-LocusZoom_Widgets-remove_panel.html index d7a742cc..ad197c8d 100644 --- a/docs/api/module-LocusZoom_Widgets-remove_panel.html +++ b/docs/api/module-LocusZoom_Widgets-remove_panel.html @@ -233,7 +233,7 @@

    Parameters:

    diff --git a/docs/api/module-LocusZoom_Widgets-resize_to_data.html b/docs/api/module-LocusZoom_Widgets-resize_to_data.html index 4cca48cc..e1fe6751 100644 --- a/docs/api/module-LocusZoom_Widgets-resize_to_data.html +++ b/docs/api/module-LocusZoom_Widgets-resize_to_data.html @@ -262,7 +262,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Widgets-set_state.html b/docs/api/module-LocusZoom_Widgets-set_state.html index 405a615c..4ea9ae2a 100644 --- a/docs/api/module-LocusZoom_Widgets-set_state.html +++ b/docs/api/module-LocusZoom_Widgets-set_state.html @@ -416,7 +416,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Widgets-shift_region.html b/docs/api/module-LocusZoom_Widgets-shift_region.html index 25643684..1519a535 100644 --- a/docs/api/module-LocusZoom_Widgets-shift_region.html +++ b/docs/api/module-LocusZoom_Widgets-shift_region.html @@ -306,7 +306,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Widgets-title.html b/docs/api/module-LocusZoom_Widgets-title.html index 3c987a6b..52078c06 100644 --- a/docs/api/module-LocusZoom_Widgets-title.html +++ b/docs/api/module-LocusZoom_Widgets-title.html @@ -255,7 +255,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Widgets-toggle_legend.html b/docs/api/module-LocusZoom_Widgets-toggle_legend.html index 0a45e0df..00782acc 100644 --- a/docs/api/module-LocusZoom_Widgets-toggle_legend.html +++ b/docs/api/module-LocusZoom_Widgets-toggle_legend.html @@ -163,7 +163,7 @@

    new togg
    diff --git a/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html b/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html index 06a8ee72..ed075de9 100644 --- a/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html +++ b/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html @@ -215,7 +215,7 @@

    Parameters:

    diff --git a/docs/api/module-LocusZoom_Widgets-zoom_region.html b/docs/api/module-LocusZoom_Widgets-zoom_region.html index 03769964..1dd1353b 100644 --- a/docs/api/module-LocusZoom_Widgets-zoom_region.html +++ b/docs/api/module-LocusZoom_Widgets-zoom_region.html @@ -306,7 +306,7 @@
    Parameters:

    diff --git a/docs/api/module-LocusZoom_Widgets.html b/docs/api/module-LocusZoom_Widgets.html index 02ce5c80..1d3cbd1a 100644 --- a/docs/api/module-LocusZoom_Widgets.html +++ b/docs/api/module-LocusZoom_Widgets.html @@ -1240,7 +1240,7 @@
    Properties:

    diff --git a/docs/api/module-components_legend-Legend.html b/docs/api/module-components_legend-Legend.html index 74d9b827..d9065a1f 100644 --- a/docs/api/module-components_legend-Legend.html +++ b/docs/api/module-components_legend-Legend.html @@ -1148,7 +1148,7 @@

    showHome

    Modules

    Classes

    Events

    Global

    +

    Home

    Modules

    Classes

    Events

    Global


    diff --git a/docs/api/module-data_requester-DataOperation.html b/docs/api/module-data_requester-DataOperation.html index b8dff6e0..76535dbb 100644 --- a/docs/api/module-data_requester-DataOperation.html +++ b/docs/api/module-data_requester-DataOperation.html @@ -240,7 +240,7 @@
    Parameters:

    diff --git a/docs/api/module-ext_lz-credible-sets.html b/docs/api/module-ext_lz-credible-sets.html index 428821f1..ef60a589 100644 --- a/docs/api/module-ext_lz-credible-sets.html +++ b/docs/api/module-ext_lz-credible-sets.html @@ -175,7 +175,7 @@

    Loading and usage


    diff --git a/docs/api/module-ext_lz-dynamic-urls.html b/docs/api/module-ext_lz-dynamic-urls.html index ad7bbf25..bbe9e1f1 100644 --- a/docs/api/module-ext_lz-dynamic-urls.html +++ b/docs/api/module-ext_lz-dynamic-urls.html @@ -877,7 +877,7 @@
    Returns:

    diff --git a/docs/api/module-ext_lz-forest-track.html b/docs/api/module-ext_lz-forest-track.html index c22d8014..2ab0a040 100644 --- a/docs/api/module-ext_lz-forest-track.html +++ b/docs/api/module-ext_lz-forest-track.html @@ -170,7 +170,7 @@

    Loading and usage


    diff --git a/docs/api/module-ext_lz-intervals-enrichment.html b/docs/api/module-ext_lz-intervals-enrichment.html index ddfcb06c..ae2c7571 100644 --- a/docs/api/module-ext_lz-intervals-enrichment.html +++ b/docs/api/module-ext_lz-intervals-enrichment.html @@ -173,7 +173,7 @@

    Loading and usage


    diff --git a/docs/api/module-ext_lz-intervals-track.html b/docs/api/module-ext_lz-intervals-track.html index 3663a1ac..553d918f 100644 --- a/docs/api/module-ext_lz-intervals-track.html +++ b/docs/api/module-ext_lz-intervals-track.html @@ -178,7 +178,7 @@

    Loading and usage


    diff --git a/docs/api/module-ext_lz-parsers.html b/docs/api/module-ext_lz-parsers.html index 305ae970..eea731e2 100644 --- a/docs/api/module-ext_lz-parsers.html +++ b/docs/api/module-ext_lz-parsers.html @@ -1265,7 +1265,7 @@
    Returns:

    diff --git a/docs/api/module-ext_lz-tabix-source.html b/docs/api/module-ext_lz-tabix-source.html index 48f9b466..a5b1dfde 100644 --- a/docs/api/module-ext_lz-tabix-source.html +++ b/docs/api/module-ext_lz-tabix-source.html @@ -179,7 +179,7 @@

    Loading and usage


    diff --git a/docs/api/module-ext_lz-widget-addons-covariates_model.html b/docs/api/module-ext_lz-widget-addons-covariates_model.html index a79ab740..7f5649d9 100644 --- a/docs/api/module-ext_lz-widget-addons-covariates_model.html +++ b/docs/api/module-ext_lz-widget-addons-covariates_model.html @@ -287,7 +287,7 @@
    Properties

    diff --git a/docs/api/module-ext_lz-widget-addons-data_layers.html b/docs/api/module-ext_lz-widget-addons-data_layers.html index bddaf6c5..5118a845 100644 --- a/docs/api/module-ext_lz-widget-addons-data_layers.html +++ b/docs/api/module-ext_lz-widget-addons-data_layers.html @@ -163,7 +163,7 @@

    new data_l
    diff --git a/docs/api/module-ext_lz-widget-addons.html b/docs/api/module-ext_lz-widget-addons.html index e50a61a2..cb8793a9 100644 --- a/docs/api/module-ext_lz-widget-addons.html +++ b/docs/api/module-ext_lz-widget-addons.html @@ -182,7 +182,7 @@

    Classes


    diff --git a/docs/api/module-registry_base-RegistryBase.html b/docs/api/module-registry_base-RegistryBase.html index 977aaa5a..8d86c3ca 100644 --- a/docs/api/module-registry_base-RegistryBase.html +++ b/docs/api/module-registry_base-RegistryBase.html @@ -987,7 +987,7 @@
    Returns:

    diff --git a/docs/api/module-undercomplicate.BaseAdapter.html b/docs/api/module-undercomplicate.BaseAdapter.html new file mode 100644 index 00000000..102915e7 --- /dev/null +++ b/docs/api/module-undercomplicate.BaseAdapter.html @@ -0,0 +1,1497 @@ + + + + + JSDoc: Class: BaseAdapter + + + + + + + + + + +
    + +

    Class: BaseAdapter

    + + + + + + +
    + +
    + +

    + undercomplicate.BaseAdapter()

    + + +
    + +
    +
    + + + + + + +

    new BaseAdapter()

    + + + + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    config.cache_enabled + + +boolean + + + + + + <optional>
    + + + + + +
    + + true + +

    Whether to enable the LRU cache, and store a copy of the normalized and parsed response data. +Turned on by default for most remote requests; turn off if you are using another datastore (like Vuex) or if the response body uses too much memory.

    config.cache_size + + +number + + + + + + <optional>
    + + + + + +
    + + 3 + +

    How many requests to cache. Track-dependent annotations like LD might benefit +from caching more items, while very large payloads (like, um, TOPMED LD) might benefit from a smaller cache size. +For most LocusZoom usages, the cache is "region aware": zooming in will use cached data, not a separate request

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +

    Methods

    + + + + + + + +

    _annotateRecords(records, options) → {*}

    + + + + + + +
    +

    Perform custom client-side operations on the retrieved data. For example, add calculated fields or +perform rapid client-side filtering on cached data. Annotations are applied after cache, which means +that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.

    +

    This result is currently not cached, but it may become so in the future as responsibility for dynamic UI +behavior moves to other layers of the application.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    records + + +Array.<Object> + + + +
    options + + +Object + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _buildRequestOptions(options, dependent_data) → {*}

    + + + + + + +
    +

    Build an object with options that control the request. This can take into account both explicit options, and prior data.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +Object + + + +

    Any global options passed in via getData. Eg, in locuszoom, every request is passed a copy of plot.state as the options object, in which case every adapter would expect certain basic information like chr, start, end to be available.

    dependent_data + + +Array.<Object> + + + +

    If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, ld(assoc) means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    An options object containing initial options, plus any calculated values relevant to the request.

    +
    + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _getCacheKey(options) → {*}

    + + + + + + +
    +

    Determine how this request is uniquely identified in cache. Usually this is an exact match for the same key, but it doesn't have to be. +The LRU cache implements a find method, which means that a cache item can optionally be identified by its node +metadata (instead of exact key match). +This is useful for situations where the user zooms in to a smaller region and wants the original request to +count as a cache hit. See subclasses for example.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +object + + + +

    Request options from _buildRequestOptions

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    This is often a string concatenating unique values for a compound cache key, like chr_start_end. If null, it is treated as a cache miss.

    +
    + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _normalizeResponse(response_text, options) → {*}

    + + + + + + +
    +

    Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    response_text + + +* + + + +

    The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON.

    options + + +Object + + + +

    Request options. These are not typically used when normalizing a response, but the object is available.

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    A list of objects, each object representing one row of data {column_name: value_for_row}

    +
    + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _performRequest(options) → {Promise}

    + + + + + + +
    +

    Perform the act of data retrieval (eg from a URL, blob, or JSON entity)

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +object + + + +

    Request options from _buildRequestOptions

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise + + +
    +
    + + + + + + + + + + + + + +

    _postProcessResponse(records, options)

    + + + + + + +
    +

    A hook to transform the response after all operations are done. For example, this can be used to prefix fields +with a namespace unique to the request, like log_pvalue -> assoc:log_pvalue. (by applying namespace prefixes to field names last, +annotations and validation can happen on the actual API payload, without having to guess what the fields were renamed to).

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    records + +
    options + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    getData(options, …dependent_data) → {Promise.<*>}

    + + + + + + +
    +

    All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDescription
    options + + +object + + + + + + + + + +

    Shared options for this request. In LocusZoom, this is typically a copy of plot.state.

    dependent_data + + +Array.<Array> + + + + + + + + + + <repeatable>
    + +

    Zero or more recordsets corresponding to each individual adapter that this one depends on. +Can be used to build a request that takes into account prior data.

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<*> + + +
    +
    + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + +
    + + + + + + + \ No newline at end of file diff --git a/docs/api/module-undercomplicate.BaseUrlAdapter.html b/docs/api/module-undercomplicate.BaseUrlAdapter.html new file mode 100644 index 00000000..9befc760 --- /dev/null +++ b/docs/api/module-undercomplicate.BaseUrlAdapter.html @@ -0,0 +1,1410 @@ + + + + + JSDoc: Class: BaseUrlAdapter + + + + + + + + + + +
    + +

    Class: BaseUrlAdapter

    + + + + + + +
    + +
    + +

    + undercomplicate.BaseUrlAdapter()

    + +

    Fetch data over the web, usually from a REST API that returns JSON

    + + +
    + +
    +
    + + + + +

    Constructor

    + + + +

    new BaseUrlAdapter()

    + + + + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    config.url + + +string + + + +

    The URL to request

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + +

    Extends

    + + + + + + + + + + + + + + + + + + + + + + +

    Methods

    + + + + + + + +

    _annotateRecords(records, options) → {*}

    + + + + + + +
    +

    Perform custom client-side operations on the retrieved data. For example, add calculated fields or +perform rapid client-side filtering on cached data. Annotations are applied after cache, which means +that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.

    +

    This result is currently not cached, but it may become so in the future as responsibility for dynamic UI +behavior moves to other layers of the application.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    records + + +Array.<Object> + + + +
    options + + +Object + + + +
    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _buildRequestOptions(options, dependent_data) → {*}

    + + + + + + +
    +

    Build an object with options that control the request. This can take into account both explicit options, and prior data.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +Object + + + +

    Any global options passed in via getData. Eg, in locuszoom, every request is passed a copy of plot.state as the options object, in which case every adapter would expect certain basic information like chr, start, end to be available.

    dependent_data + + +Array.<Object> + + + +

    If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, ld(assoc) means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    An options object containing initial options, plus any calculated values relevant to the request.

    +
    + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _getCacheKey()

    + + + + + + +
    +

    Default cache key is the URL for this request

    +
    + + + + + + + + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    _normalizeResponse(response_text, options) → {*}

    + + + + + + +
    +

    Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    response_text + + +* + + + +

    The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON.

    options + + +Object + + + +

    Request options. These are not typically used when normalizing a response, but the object is available.

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    A list of objects, each object representing one row of data {column_name: value_for_row}

    +
    + + + +
    +
    + Type +
    +
    + +* + + +
    +
    + + + + + + + + + + + + + +

    _performRequest(options) → {Promise}

    + + + + + + +
    +

    Perform the act of data retrieval (eg from a URL, blob, or JSON entity)

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +object + + + +

    Request options from _buildRequestOptions

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise + + +
    +
    + + + + + + + + + + + + + +

    _postProcessResponse(records, options)

    + + + + + + +
    +

    A hook to transform the response after all operations are done. For example, this can be used to prefix fields +with a namespace unique to the request, like log_pvalue -> assoc:log_pvalue. (by applying namespace prefixes to field names last, +annotations and validation can happen on the actual API payload, without having to guess what the fields were renamed to).

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    records + +
    options + +
    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    getData(options, …dependent_data) → {Promise.<*>}

    + + + + + + +
    +

    All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDescription
    options + + +object + + + + + + + + + +

    Shared options for this request. In LocusZoom, this is typically a copy of plot.state.

    dependent_data + + +Array.<Array> + + + + + + + + + + <repeatable>
    + +

    Zero or more recordsets corresponding to each individual adapter that this one depends on. +Can be used to build a request that takes into account prior data.

    + + + + + + +
    + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Promise.<*> + + +
    +
    + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + +
    + + + + + + + \ No newline at end of file diff --git a/docs/api/module-undercomplicate.LRUCache.html b/docs/api/module-undercomplicate.LRUCache.html new file mode 100644 index 00000000..c81af78d --- /dev/null +++ b/docs/api/module-undercomplicate.LRUCache.html @@ -0,0 +1,240 @@ + + + + + JSDoc: Class: LRUCache + + + + + + + + + + +
    + +

    Class: LRUCache

    + + + + + + +
    + +
    + +

    + undercomplicate.LRUCache(max_sizeopt)

    + + +
    + +
    +
    + + + + + + +

    new LRUCache(max_sizeopt)

    + + + + + + +
    +

    Least-recently used cache implementation, with "approximate match" semantics

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    max_size + + +number + + + + + + <optional>
    + + + + + +
    + + 3 + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + +
    + + + + + + + \ No newline at end of file diff --git a/docs/api/module-undercomplicate.html b/docs/api/module-undercomplicate.html new file mode 100644 index 00000000..ad94f3d1 --- /dev/null +++ b/docs/api/module-undercomplicate.html @@ -0,0 +1,1046 @@ + + + + + JSDoc: Module: undercomplicate + + + + + + + + + + +
    + +

    Module: undercomplicate

    + + + + + + +
    + +
    + + + + + +
    + +
    +
    + + +

    The LocusZoom data retrieval library was originally created as a standalone library, mainly for LZ usage. +It is inlined into the LocusZoom source code, because there are no other places it is really used, and JSDoc references are much easier +to generate with a package in the same repo.

    +

    See individual adapter classes (and their documentation) for helpful guides on what methods are available, and common customizations for LocusZoom use.

    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + +
    See:
    +
    + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +

    Classes

    + +
    +
    LLNode
    +
    + +
    BaseAdapter
    +
    + +
    BaseUrlAdapter
    +
    + +
    LRUCache
    +
    +
    + + + + + + + + + + + +

    Methods

    + + + + + + + +

    (static) full_outer_match(left, right, left_key, right_key) → {Array.<Object>}

    + + + + + + +
    +

    Equivalent to FULL OUTER JOIN in SQL. Return records in either recordset, joined where appropriate.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    left + + +Array.<object> + + + +

    The left side recordset

    right + + +Array.<object> + + + +

    The right side recordset

    left_key + + +string + + + +

    The join field in left records

    right_key + + +string + + + +

    The join field in right records

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + + + + + + + + + + +

    (static) groupBy(records, group_key) → {Map.<any, any>}

    + + + + + + +
    +

    Simple grouping function, used to identify sets of records for joining.

    +

    Used internally by join helpers, exported mainly for unit testing

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    records + + +Array.<object> + + + +
    group_key + + +string + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Map.<any, any> + + +
    +
    + + + + + + + + + + + + + +

    (static) inner_match(left, right, left_key, right_key) → {Array.<Object>}

    + + + + + + +
    +

    Equivalent to INNER JOIN in SQL. Only return record joins if the key field has a match on both left and right.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    left + + +Array.<object> + + + +

    The left side recordset

    right + + +Array.<object> + + + +

    The right side recordset

    left_key + + +string + + + +

    The join field in left records

    right_key + + +string + + + +

    The join field in right records

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + + + + + + + + + + +

    (static) left_match(left, right, left_key, right_key) → {Array.<Object>}

    + + + + + + +
    +

    Equivalent to LEFT OUTER JOIN in SQL. Return all left records, joined to matching right data where appropriate.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    left + + +Array.<Object> + + + +

    The left side recordset

    right + + +Array.<Object> + + + +

    The right side recordset

    left_key + + +string + + + +

    The join field in left records

    right_key + + +string + + + +

    The join field in right records

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + +
    + + + + + + + \ No newline at end of file diff --git a/docs/api/registry_adapters.js.html b/docs/api/registry_adapters.js.html index 9eebd64e..5d63a6c7 100644 --- a/docs/api/registry_adapters.js.html +++ b/docs/api/registry_adapters.js.html @@ -80,7 +80,7 @@

    Source: registry/adapters.js


    diff --git a/docs/api/registry_base.js.html b/docs/api/registry_base.js.html index bfe2c423..a4eb5ae1 100644 --- a/docs/api/registry_base.js.html +++ b/docs/api/registry_base.js.html @@ -161,7 +161,7 @@

    Source: registry/base.js


    diff --git a/docs/api/registry_data_layers.js.html b/docs/api/registry_data_layers.js.html index df124d92..5cc85c9d 100644 --- a/docs/api/registry_data_layers.js.html +++ b/docs/api/registry_data_layers.js.html @@ -55,7 +55,7 @@

    Source: registry/data_layers.js


    diff --git a/docs/api/registry_data_ops.js.html b/docs/api/registry_data_ops.js.html index 19134e73..26fae5c3 100644 --- a/docs/api/registry_data_ops.js.html +++ b/docs/api/registry_data_ops.js.html @@ -54,7 +54,7 @@

    Source: registry/data_ops.js

    * * @module LocusZoom_DataFunctions */ -import {joins} from 'undercomplicate'; +import {joins} from '../data/undercomplicate'; import {RegistryBase} from './base'; @@ -209,7 +209,7 @@

    Source: registry/data_ops.js


    diff --git a/docs/api/registry_layouts.js.html b/docs/api/registry_layouts.js.html index 47487de5..a6116f4d 100644 --- a/docs/api/registry_layouts.js.html +++ b/docs/api/registry_layouts.js.html @@ -180,7 +180,7 @@

    Source: registry/layouts.js


    diff --git a/docs/api/registry_matchers.js.html b/docs/api/registry_matchers.js.html index 440a1dbc..9431a7da 100644 --- a/docs/api/registry_matchers.js.html +++ b/docs/api/registry_matchers.js.html @@ -154,7 +154,7 @@

    Source: registry/matchers.js


    diff --git a/docs/api/registry_transforms.js.html b/docs/api/registry_transforms.js.html index 0260ab41..7ebd4167 100644 --- a/docs/api/registry_transforms.js.html +++ b/docs/api/registry_transforms.js.html @@ -51,7 +51,7 @@

    Source: registry/transforms.js

    return (value) => { return funcs.reduce( (acc, func) => func(acc), - value + value, ); }; } @@ -107,7 +107,7 @@

    Source: registry/transforms.js


    diff --git a/docs/api/registry_widgets.js.html b/docs/api/registry_widgets.js.html index 9589cf29..2d22157f 100644 --- a/docs/api/registry_widgets.js.html +++ b/docs/api/registry_widgets.js.html @@ -53,7 +53,7 @@

    Source: registry/widgets.js


    diff --git a/docs/guides/data_retrieval.html b/docs/guides/data_retrieval.html index eec8cd36..5e596093 100644 --- a/docs/guides/data_retrieval.html +++ b/docs/guides/data_retrieval.html @@ -111,7 +111,7 @@

    Table of Contents

  • Creating your own custom adapter
    • Can I reuse existing code?
    • -
    • Re-using code via subclasses
    • +
    • Customizing data retrieval via subclasses
    • What happens during a data request?
      • Step 1: Fetching data from a remote server and converting to records
      • @@ -241,23 +241,34 @@

        Can I reuse existing code?

      • If the actual headers, body, or request method must be customized in order to carry out the request. This happens when data is retrieved from a particular technology (REST vs GraphQL vs Tabix), or if the request must incorporate some form of authentication credentials.
      • If some of the fields in your custom API format need to be transformed or renamed in order to match expectations in LZ code. For example, the LD adapter may try to suggest an LD reference variant by looking for the GWAS variant with the largest log_pvalue. (over time, built-in LZ adapters trend towards being more strict about field names; it is easier to write reliable code when not having to deal with unpredictable data!)
      • -

        Re-using code via subclasses

        +

        Customizing data retrieval via subclasses

        Most custom sites will only need to change very small things to work with their data. For example, if your REST API uses the same payload format as the UM PortalDev API, but a different way of constructing queries, you can change just one function and define a new data adapter:

        const AssociationLZ = LocusZoom.Adapters.get('AssociationLZ');
         class CustomAssociation extends AssociationLZ {
             _getURL(request_options) {
                 // Every adapter receives the info from plot.state, plus any additional request options calculated/added in the function `_buildRequestOptions`
        -      // The inputs to the function can be used to influence what query is constructed. Eg, since the current view region is stored in `plot.state`:
        +        // The inputs to the function can be used to influence what query is constructed. Eg, since the current view region is stored in `plot.state`:
                 const {chr, start, end} = request_options;
                 // Fetch the region of interest from a hypothetical REST API that uses query parameters to define the region query, for a given study URL such as `data.example/gwas/<id>/?chr=_&start=_&end=_`
                 return `${this._url}/${this.source}/?chr=${encodeURIComponent(chr)}&start=${encodeURIComponent(start)}&end${encodeURIComponent(end)}`
           }
        -}
        -// A custom adapter should be added to the registry before using it
        -LocusZoom.Adapters.add('CustomAssociation', CustomAssociation);
        -
        -// From there, it can be used anywhere throughout LocusZoom, in the same way as any built-in adapter
        -data_sources.add('mystudy', ['CustomAssociation', {url: 'https://data.example/gwas', source: 42 }]);
        + + _annotateRecords(records, options) { + // Imagine that your API returns a payload that mostly works, but a few fields are a little different than what is expected. + // Usually LocusZoom is pretty tolerant of changing field names- but because Association plots connect to so many extra annotations, certain magic features require a little extra attention to detail + records.forEach((item) => { + // LocusZoom connects assoc summstats to other datasets, by finding the most significant variant. To find that variant and ask for LD, it expects two special fields with very specific names, and sort of specific formats. If you already have these fields, great- if not, they can be manually added by custom code, even if no one will let you change the API server directly + item.log_pvalue = -Math.log10(item.pValue); // It's better if you send log_pvalue from the server directly, not calculate it on the front end (JS is subject to numerical underflow, and sending pValue to the browser may cause numerical underflow problems) + item.variant = `${item.chrom}:${item.pos}_${item.ref}/${item.alt}`; + }); + return records; + } +} +// A custom adapter should be added to the registry before using it +LocusZoom.Adapters.add('CustomAssociation', CustomAssociation); + +// From there, it can be used anywhere throughout LocusZoom, in the same way as any built-in adapter +data_sources.add('mystudy', ['CustomAssociation', {url: 'https://data.example/gwas', source: 42 }]);

        In the above example, an HTTP GET request will be sent to the server every time that new data is requested. If further control is required (like sending a POST request with custom body), you may need to override additional methods such as fetchRequest. See below for more information, then consult the detailed developer documentation for details.

        Common types of data retrieval that are most often customized:

          diff --git a/esm/version.js b/esm/version.js index 063c08b3..314a8c3c 100644 --- a/esm/version.js +++ b/esm/version.js @@ -1 +1 @@ -export default '0.14.0-beta.2'; +export default '0.14.0-beta.3'; diff --git a/index.html b/index.html index b722e3fc..7d51b391 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,7 @@

          Get LocusZoom.js

          CSS
          - https://cdn.jsdelivr.net/npm/locuszoom@0.14.0-beta.1/dist/locuszoom.css + https://cdn.jsdelivr.net/npm/locuszoom@0.14.0-beta.3/dist/locuszoom.css
          All CSS classes are namespaced to avoid collisions. Use <link crossorigin="anonymous" ...> to ensure that saving images works correctly.
          @@ -96,7 +96,7 @@
          Dependencies
          Javascript
          diff --git a/package-lock.json b/package-lock.json index f49e8a5b..b8b9f20c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.14.0-beta.2", + "version": "0.14.0-beta.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ff6e2ad7..dd3eb3da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.14.0-beta.2", + "version": "0.14.0-beta.3", "main": "dist/locuszoom.app.min.js", "module": "esm/index.js", "sideEffects": true, From 7169a5c92d9c50391fa54c0a4e8f73f0224854d0 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 28 Feb 2022 12:32:46 -0500 Subject: [PATCH 098/100] Reduce "resizeobserver loop limit exceeded" errors by using RAF for resizes (keeps listener on window, rather than resizeobserver, because normal Lz actions can cause the container to resize and we don't want out of control iterative redraws) --- esm/components/plot.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esm/components/plot.js b/esm/components/plot.js index 9a7eb5aa..a4dcb2e2 100644 --- a/esm/components/plot.js +++ b/esm/components/plot.js @@ -1116,7 +1116,8 @@ class Plot { d3.select(this.container).classed('lz-container-responsive', true); // If this is a responsive layout then set a namespaced/unique onresize event listener on the window - const resize_listener = () => this.rescaleSVG(); + const resize_listener = () => window.requestAnimationFrame(() => this.rescaleSVG()); + window.addEventListener('resize', resize_listener); this.trackExternalListener(window, 'resize', resize_listener); From 674d25cc7fb338ff6c2b378e538e6de2b0ec5a8a Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Mon, 28 Feb 2022 12:57:18 -0500 Subject: [PATCH 099/100] Bump and add assets for 0.14.0-beta.4 --- dist/ext/lz-aggregation-tests.min.js | 2 +- dist/ext/lz-credible-sets.min.js | 2 +- dist/ext/lz-dynamic-urls.min.js | 2 +- dist/ext/lz-forest-track.min.js | 2 +- dist/ext/lz-intervals-enrichment.min.js | 2 +- dist/ext/lz-intervals-track.min.js | 2 +- dist/ext/lz-parsers.min.js | 2 +- dist/ext/lz-tabix-source.min.js | 2 +- dist/ext/lz-widget-addons.min.js | 2 +- dist/locuszoom.app.min.js | 4 +- dist/locuszoom.app.min.js.map | 2 +- docs/api/LLNode.html | 2 +- docs/api/LayoutRegistry.html | 2 +- docs/api/Line.html | 2 +- docs/api/Panel.html | 2 +- docs/api/Plot.html | 2 +- docs/api/TransformationFunctionsRegistry.html | 2 +- ...onents_data_layer_annotation_track.js.html | 2 +- docs/api/components_data_layer_arcs.js.html | 2 +- docs/api/components_data_layer_base.js.html | 2 +- docs/api/components_data_layer_genes.js.html | 2 +- ...nents_data_layer_highlight_regions.js.html | 2 +- docs/api/components_data_layer_line.js.html | 2 +- .../api/components_data_layer_scatter.js.html | 2 +- docs/api/components_legend.js.html | 2 +- docs/api/components_panel.js.html | 2 +- docs/api/components_plot.js.html | 5 +- docs/api/components_toolbar_index.js.html | 2 +- docs/api/components_toolbar_widgets.js.html | 2 +- docs/api/data_adapters.js.html | 2 +- docs/api/data_field.js.html | 2 +- docs/api/data_requester.js.html | 2 +- docs/api/data_sources.js.html | 2 +- docs/api/data_undercomplicate_adapter.js.html | 2 +- docs/api/data_undercomplicate_index.js.html | 2 +- docs/api/data_undercomplicate_joins.js.html | 2 +- .../data_undercomplicate_lru_cache.js.html | 2 +- .../api/data_undercomplicate_requests.js.html | 2 +- docs/api/data_undercomplicate_util.js.html | 2 +- docs/api/ext_lz-credible-sets.js.html | 2 +- docs/api/ext_lz-dynamic-urls.js.html | 2 +- docs/api/ext_lz-forest-track.js.html | 2 +- docs/api/ext_lz-intervals-enrichment.js.html | 2 +- docs/api/ext_lz-intervals-track.js.html | 2 +- docs/api/ext_lz-parsers_bed.js.html | 2 +- docs/api/ext_lz-parsers_gwas_parsers.js.html | 2 +- docs/api/ext_lz-parsers_index.js.html | 2 +- docs/api/ext_lz-parsers_ld.js.html | 2 +- docs/api/ext_lz-tabix-source.js.html | 2 +- docs/api/ext_lz-widget-addons.js.html | 2 +- docs/api/global.html | 2 +- docs/api/helpers_common.js.html | 2 +- docs/api/helpers_display.js.html | 2 +- docs/api/helpers_jsonpath.js.html | 2 +- docs/api/helpers_layouts.js.html | 2 +- docs/api/helpers_render.js.html | 2 +- docs/api/helpers_scalable.js.html | 2 +- docs/api/helpers_transforms.js.html | 2 +- docs/api/index.html | 2 +- docs/api/index.js.html | 2 +- docs/api/layouts_index.js.html | 2 +- docs/api/module-LocusZoom-DataSources.html | 2 +- docs/api/module-LocusZoom.html | 2 +- ...dule-LocusZoom_Adapters-AssociationLZ.html | 2 +- ...module-LocusZoom_Adapters-BaseAdapter.html | 2 +- ...ule-LocusZoom_Adapters-BaseApiAdapter.html | 2 +- ...dule-LocusZoom_Adapters-BaseLZAdapter.html | 2 +- ...dule-LocusZoom_Adapters-BaseUMAdapter.html | 2 +- ...dule-LocusZoom_Adapters-CredibleSetLZ.html | 2 +- ...e-LocusZoom_Adapters-GeneConstraintLZ.html | 2 +- .../api/module-LocusZoom_Adapters-GeneLZ.html | 2 +- ...dule-LocusZoom_Adapters-GwasCatalogLZ.html | 2 +- .../module-LocusZoom_Adapters-IntervalLZ.html | 2 +- .../module-LocusZoom_Adapters-LDServer.html | 2 +- .../module-LocusZoom_Adapters-PheWASLZ.html | 2 +- .../module-LocusZoom_Adapters-RecombLZ.html | 2 +- ...odule-LocusZoom_Adapters-StaticSource.html | 2 +- ...ule-LocusZoom_Adapters-TabixUrlSource.html | 2 +- ...module-LocusZoom_Adapters-UserTabixLD.html | 2 +- docs/api/module-LocusZoom_Adapters.html | 2 +- docs/api/module-LocusZoom_DataFunctions.html | 2 +- ...le-LocusZoom_DataLayers-BaseDataLayer.html | 2 +- ...LocusZoom_DataLayers-annotation_track.html | 2 +- .../api/module-LocusZoom_DataLayers-arcs.html | 2 +- ...-LocusZoom_DataLayers-category_forest.html | 2 +- ...LocusZoom_DataLayers-category_scatter.html | 2 +- .../module-LocusZoom_DataLayers-forest.html | 2 +- .../module-LocusZoom_DataLayers-genes.html | 2 +- ...ocusZoom_DataLayers-highlight_regions.html | 2 +- ...module-LocusZoom_DataLayers-intervals.html | 2 +- ...sZoom_DataLayers-intervals_enrichment.html | 2 +- ...-LocusZoom_DataLayers-orthogonal_line.html | 2 +- .../module-LocusZoom_DataLayers-scatter.html | 2 +- docs/api/module-LocusZoom_DataLayers.html | 2 +- docs/api/module-LocusZoom_Layouts.html | 2 +- docs/api/module-LocusZoom_MatchFunctions.html | 2 +- docs/api/module-LocusZoom_ScaleFunctions.html | 2 +- ...ule-LocusZoom_TransformationFunctions.html | 2 +- .../module-LocusZoom_Widgets-BaseWidget.html | 2 +- .../api/module-LocusZoom_Widgets-_Button.html | 2 +- ...ule-LocusZoom_Widgets-display_options.html | 2 +- ...module-LocusZoom_Widgets-download_png.html | 2 +- ...module-LocusZoom_Widgets-download_svg.html | 2 +- ...module-LocusZoom_Widgets-filter_field.html | 2 +- docs/api/module-LocusZoom_Widgets-menu.html | 2 +- ...ule-LocusZoom_Widgets-move_panel_down.html | 2 +- ...odule-LocusZoom_Widgets-move_panel_up.html | 2 +- ...module-LocusZoom_Widgets-region_scale.html | 2 +- ...module-LocusZoom_Widgets-remove_panel.html | 2 +- ...dule-LocusZoom_Widgets-resize_to_data.html | 2 +- .../module-LocusZoom_Widgets-set_state.html | 2 +- ...module-LocusZoom_Widgets-shift_region.html | 2 +- docs/api/module-LocusZoom_Widgets-title.html | 2 +- ...odule-LocusZoom_Widgets-toggle_legend.html | 2 +- ...LocusZoom_Widgets-toggle_split_tracks.html | 2 +- .../module-LocusZoom_Widgets-zoom_region.html | 2 +- docs/api/module-LocusZoom_Widgets.html | 2 +- docs/api/module-components_legend-Legend.html | 2 +- .../module-data_requester-DataOperation.html | 2 +- docs/api/module-ext_lz-credible-sets.html | 2 +- docs/api/module-ext_lz-dynamic-urls.html | 2 +- docs/api/module-ext_lz-forest-track.html | 2 +- .../module-ext_lz-intervals-enrichment.html | 2 +- docs/api/module-ext_lz-intervals-track.html | 2 +- docs/api/module-ext_lz-parsers.html | 2 +- docs/api/module-ext_lz-tabix-source.html | 2 +- ...ext_lz-widget-addons-covariates_model.html | 2 +- ...dule-ext_lz-widget-addons-data_layers.html | 2 +- docs/api/module-ext_lz-widget-addons.html | 2 +- .../module-registry_base-RegistryBase.html | 2 +- .../module-undercomplicate.BaseAdapter.html | 2 +- ...module-undercomplicate.BaseUrlAdapter.html | 2 +- docs/api/module-undercomplicate.LRUCache.html | 2 +- docs/api/module-undercomplicate.html | 2 +- docs/api/registry_adapters.js.html | 2 +- docs/api/registry_base.js.html | 2 +- docs/api/registry_data_layers.js.html | 2 +- docs/api/registry_data_ops.js.html | 2 +- docs/api/registry_layouts.js.html | 2 +- docs/api/registry_matchers.js.html | 2 +- docs/api/registry_transforms.js.html | 2 +- docs/api/registry_widgets.js.html | 2 +- esm/version.js | 2 +- index.html | 4 +- package-lock.json | 186 ++++++++++-------- package.json | 4 +- 146 files changed, 254 insertions(+), 231 deletions(-) diff --git a/dist/ext/lz-aggregation-tests.min.js b/dist/ext/lz-aggregation-tests.min.js index d1e58698..11cfbc06 100644 --- a/dist/ext/lz-aggregation-tests.min.js +++ b/dist/ext/lz-aggregation-tests.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.3 */ +/*! Locuszoom 0.14.0-beta.4 */ var LzAggregationTests;(()=>{"use strict";var e={d:(t,r)=>{for(var s in r)e.o(r,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:r[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>o});const r=raremetal;function s(e){const t=e.Adapters.get("BaseLZAdapter");e.DataFunctions.add("gene_plus_aggregation",((e,[t,r])=>{const s={};return r.groups.forEach((function(e){Object.prototype.hasOwnProperty.call(s,e.group)||(s[e.group]=[]),s[e.group].push(e.pvalue)})),t.forEach((e=>{const t=e.gene_name,r=s[t];r&&(e.aggregation_best_pvalue=Math.min.apply(null,r))})),t})),e.Adapters.add("AggregationTestSourceLZ",class extends t{constructor(e){e.prefix_namespace=!1,super(e)}_buildRequestOptions(e){const{aggregation_tests:t={}}=e,{genoset_id:r=null,genoset_build:s=null,phenoset_build:o=null,pheno:n=null,calcs:a={},masks:u=[]}=t;return t.mask_ids=u.map((e=>e.name)),e.aggregation_tests=t,e}_getURL(e){return this._url}_getCacheKey(e){const{chr:t,start:r,end:s,aggregation_tests:o}=e,{genoset_id:n=null,genoset_build:a=null,phenoset_id:u=null,pheno:l=null,mask_ids:g}=o;return JSON.stringify({chrom:t,start:r,stop:s,genotypeDataset:n,phenotypeDataset:u,phenotype:l,samples:"ALL",genomeBuild:a,masks:g})}_performRequest(e){const t=this._getURL(e),r=this._getCacheKey(e);return fetch(t,{method:"POST",body:r,headers:{"Content-Type":"application/json"}}).then((e=>{if(!e.ok)throw new Error(e.statusText);return e.text()})).then((e=>{const t="string"==typeof e?JSON.parse(e):e;if(t.error)throw new Error(t.error);return t.data}))}_annotateRecords(e,t){const{aggregation_tests:s}=t,{calcs:o=[],mask_ids:n=[],masks:a=[]}=s;if(!e.groups)return{groups:[],variants:[]};e.groups=e.groups.filter((e=>"GENE"===e.groupType));const u=r.helpers.parsePortalJSON(e);let l=u[0];const g=u[1];if(l=l.byMask(n),!o||0===Object.keys(o).length)return{variants:[],groups:[],results:[]};return new r.helpers.PortalTestRunner(l,g,o).toJSON().then((function(e){const t=a.reduce(((e,t)=>(e[t.name]=t.description,e)),{});return e.data.groups.forEach((e=>{e.mask_name=t[e.mask]})),e.data})).catch((function(e){throw console.error(e),new Error("Failed to calculate aggregation test results")}))}}),e.Adapters.add("AssocFromAggregationLZ",class extends t{_buildRequestOptions(e,t){if(!t)throw new Error("Aggregation test results must be provided");return e._agg_results=t,e}_performRequest(e){return Promise.resolve(e._agg_results.variants)}_normalizeResponse(e){const t=new RegExp("(?:chr)?(.+):(\\d+)_?(\\w+)?/?([^_]+)?_?(.*)?");return e.map((e=>{const{variant:r,altFreq:s,pvalue:o}=e,n=r.match(t),[a,u,l,g]=n;return{variant:r,chromosome:u,position:+l,ref_allele:g,ref_allele_freq:1-s,log_pvalue:-Math.log10(o)}})).sort(((e,t)=>(e=e.variant)<(t=t.variant)?-1:e>t?1:0))}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(s);const o=s;LzAggregationTests=t.default})(); //# sourceMappingURL=lz-aggregation-tests.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-credible-sets.min.js b/dist/ext/lz-credible-sets.min.js index dbc3b5e2..48ce77b8 100644 --- a/dist/ext/lz-credible-sets.min.js +++ b/dist/ext/lz-credible-sets.min.js @@ -1,4 +1,4 @@ -/*! Locuszoom 0.14.0-beta.3 */ +/*! Locuszoom 0.14.0-beta.4 */ var LzCredibleSets;(()=>{var e={803:function(e){var t;t=function(){return function(e){var t={};function r(o){if(t[o])return t[o].exports;var a=t[o]={i:o,l:!1,exports:{}};return e[o].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(e,t,r){"use strict"; /** * @module stats diff --git a/dist/ext/lz-dynamic-urls.min.js b/dist/ext/lz-dynamic-urls.min.js index 073526e1..639996f1 100644 --- a/dist/ext/lz-dynamic-urls.min.js +++ b/dist/ext/lz-dynamic-urls.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.3 */ +/*! Locuszoom 0.14.0-beta.4 */ var LzDynamicUrls;(()=>{"use strict";var t={d:(n,e)=>{for(var o in e)t.o(e,o)&&!t.o(n,o)&&Object.defineProperty(n,o,{enumerable:!0,get:e[o]})},o:(t,n)=>Object.prototype.hasOwnProperty.call(t,n)},n={};function e(t){const n={};if(t){const e=("?"===t[0]?t.substr(1):t).split("&");for(let t=0;ta});const a={paramsFromUrl:s,extractValues:o,plotUpdatesUrl:function(t,n,o){o=o||r;const c=function(c){const r=e(window.location.search),s=o(t,n,c),a=Object.assign({},r,s);if(Object.keys(a).some((function(t){return r[t]!=a[t]}))){const t=(i=a,`?${Object.keys(i).map((function(t){return`${encodeURIComponent(t)}=${encodeURIComponent(i[t])}`})).join("&")}`);Object.keys(r).length?history.pushState({},document.title,t):history.replaceState({},document.title,t)}var i};return t.on("state_changed",c),c},plotWatchesUrl:function(t,n,e){e=e||c;const o=function(o){const c=s(n);e(t,c)};return window.addEventListener("popstate",o),t.trackExternalListener(window,"popstate",o),o}};LzDynamicUrls=n.default})(); //# sourceMappingURL=lz-dynamic-urls.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-forest-track.min.js b/dist/ext/lz-forest-track.min.js index 0ed8b51a..7aeac1df 100644 --- a/dist/ext/lz-forest-track.min.js +++ b/dist/ext/lz-forest-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.3 */ +/*! Locuszoom 0.14.0-beta.4 */ var LzForestTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var i in a)t.o(a,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:a[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>s});const a=d3;function i(t){const e=t.DataLayers.get("BaseDataLayer"),i={point_size:40,point_shape:"square",color:"#888888",fill_opacity:1,y_axis:{axis:2},id_field:"id",confidence_intervals:{start_field:"ci_start",end_field:"ci_end"}};class s extends e{constructor(e){e=t.Layouts.merge(e,i),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),a=`y${this.layout.y_axis.axis}_scale`,i=this.parent[a](t.data[this.layout.y_axis.field]),s=this.resolveScalableParameter(this.layout.point_size,t.data),r=Math.sqrt(s/Math.PI);return{x_min:e-r,x_max:e+r,y_min:i-r,y_max:i+r}}render(){const t=this._applyFilters(),e=`y${this.layout.y_axis.axis}_scale`;if(this.layout.confidence_intervals&&this.layout.confidence_intervals.start_field&&this.layout.confidence_intervals.end_field){const a=this.svg.group.selectAll("rect.lz-data_layer-forest.lz-data_layer-forest-ci").data(t,(t=>t[this.layout.id_field])),i=t=>{let a=this.parent.x_scale(t[this.layout.confidence_intervals.start_field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`},s=t=>{const{start_field:e,end_field:a}=this.layout.confidence_intervals,i=this.parent.x_scale,s=i(t[a])-i(t[e]);return Math.max(s,1)},r=1;a.enter().append("rect").attr("class","lz-data_layer-forest lz-data_layer-forest-ci").attr("id",(t=>`${this.getElementId(t)}_ci`)).attr("transform",`translate(0, ${isNaN(this.parent.layout.height)?0:this.parent.layout.height})`).merge(a).attr("transform",i).attr("width",s).attr("height",r),a.exit().remove()}const i=this.svg.group.selectAll("path.lz-data_layer-forest.lz-data_layer-forest-point").data(t,(t=>t[this.layout.id_field])),s=isNaN(this.parent.layout.height)?0:this.parent.layout.height,r=a.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>{const i=this.resolveScalableParameter(this.layout.point_shape,t,e),s=`symbol${i.charAt(0).toUpperCase()+i.slice(1)}`;return a[s]||null}));i.enter().append("path").attr("class","lz-data_layer-forest lz-data_layer-forest-point").attr("id",(t=>this.getElementId(t))).attr("transform",`translate(0, ${s})`).merge(i).attr("transform",(t=>{let a=this.parent.x_scale(t[this.layout.x_axis.field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`})).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>{this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}}t.DataLayers.add("forest",s),t.DataLayers.add("category_forest",class extends s{_getDataExtent(t,e){const{confidence_intervals:i}=this.layout;if(i&&i.start_field&&i.end_field){const e=t=>+t[i.start_field],s=t=>+t[i.end_field];return[a.min(t,e),a.max(t,s)]}return super._getDataExtent(t,e)}getTicks(t,e){if(!["x","y1","y2"].includes(t))throw new Error(`Invalid dimension identifier ${t}`);if(t===`y${this.layout.y_axis.axis}`){const t=this.layout.y_axis.category_field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify category_field`);return this.data.map(((e,a)=>({y:a+1,text:e[t]})))}return[]}applyCustomDataMethods(){const t=this.layout.y_axis.field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify yaxis.field`);return this.data=this.data.map(((e,a)=>(e[t]=a+1,e))),this.layout.y_axis.floor=0,this.layout.y_axis.ceiling=this.data.length+1,this}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(i);const s=i;LzForestTrack=e.default})(); //# sourceMappingURL=lz-forest-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-enrichment.min.js b/dist/ext/lz-intervals-enrichment.min.js index bd4b7306..78ba3895 100644 --- a/dist/ext/lz-intervals-enrichment.min.js +++ b/dist/ext/lz-intervals-enrichment.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.3 */ +/*! Locuszoom 0.14.0-beta.4 */ var LzIntervalsEnrichment;(()=>{"use strict";var e={d:(t,a)=>{for(var i in a)e.o(a,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:a[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>n});const a=Symbol.for("lzXCS"),i=Symbol.for("lzYCS"),s=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function r(e){const t={start_field:"start",end_field:"end",track_height:10,track_vertical_spacing:3,bounding_box_padding:2,color:"#B8B8B8",fill_opacity:.5,tooltip_positioning:"vertical"},r=e.DataLayers.get("BaseDataLayer");const n={namespace:{intervals:"intervals"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Tissue: {{intervals:tissueId|htmlescape}}
          \n Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
          \n -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
          \n Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}"},o={id:"intervals_enrichment",type:"intervals_enrichment",tag:"intervals_enrichment",namespace:{intervals:"intervals"},match:{send:"intervals:tissueId"},id_field:"intervals:start",start_field:"intervals:start",end_field:"intervals:end",filters:[{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],y_axis:{axis:1,field:"intervals:fold",floor:0,upper_buffer:.1,min_extent:[0,10]},fill_opacity:.5,color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:n},c={id:"interval_matches",type:"highlight_regions",namespace:{intervals:"intervals"},match:{receive:"intervals:tissueId"},start_field:"intervals:start",end_field:"intervals:end",merge_field:"intervals:tissueId",filters:[{field:"lz_is_match",operator:"=",value:!0},{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],fill_opacity:.1},d={id:"intervals_enrichment",tag:"intervals_enrichment",min_height:250,height:250,margin:{top:35,right:50,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:34,tick_format:"region",extent:"state"},y1:{label:"enrichment (n-fold)",label_offset:40}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[o]},h={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:e.Layouts.get("toolbar","standard_association"),panels:[function(){const t=e.Layouts.get("panel","association");return t.data_layers.unshift(c),t}(),d,e.Layouts.get("panel","genes")]};e.DataLayers.add("intervals_enrichment",class extends r{constructor(a){e.Layouts.merge(a,t),super(...arguments)}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}render(){const e=this._applyFilters(this.data),{start_field:t,end_field:r,bounding_box_padding:n,track_height:o}=this.layout,c=this.layout.y_axis.field,d=`y${this.layout.y_axis.axis}_scale`,{x_scale:h,[d]:f}=this.parent;e.forEach((e=>{e[a]=h(e[t]),e[s]=h(e[r]),e[i]=f(e[c])-this.getTrackHeight()/2+n,e[l]=e[i]+o})),e.sort(((e,t)=>{const i=e[s]-e[a];return t[s]-t[a]-i}));const _=this.svg.group.selectAll("rect").data(e);_.enter().append("rect").merge(_).attr("id",(e=>this.getElementId(e))).attr("x",(e=>e[a])).attr("y",(e=>e[i])).attr("width",(e=>e[s]-e[a])).attr("height",this.layout.track_height).attr("fill",((e,t)=>this.resolveScalableParameter(this.layout.color,e,t))).attr("fill-opacity",((e,t)=>this.resolveScalableParameter(this.layout.fill_opacity,e,t))),_.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this))}_getTooltipPosition(e){return{x_min:e.data[a],x_max:e.data[s],y_min:e.data[i],y_max:e.data[l]}}}),e.Layouts.add("tooltip","intervals_enrichment",n),e.Layouts.add("data_layer","intervals_enrichment",o),e.Layouts.add("panel","intervals_enrichment",d),e.Layouts.add("plot","intervals_association_enrichment",h)}"undefined"!=typeof LocusZoom&&LocusZoom.use(r);const n=r;LzIntervalsEnrichment=t.default})(); //# sourceMappingURL=lz-intervals-enrichment.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-track.min.js b/dist/ext/lz-intervals-track.min.js index fb4afc9a..63ea77d9 100644 --- a/dist/ext/lz-intervals-track.min.js +++ b/dist/ext/lz-intervals-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.3 */ +/*! Locuszoom 0.14.0-beta.4 */ var LzIntervalsTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var r in a)t.o(a,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>o});const a=d3,r=Symbol.for("lzXCS"),s=Symbol.for("lzYCS"),i=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function n(t){const e=t.Adapters.get("BaseUMAdapter"),n=t.Widgets.get("_Button"),o=t.Widgets.get("BaseWidget");function c(t,e){return e?`rgb(${e})`:null}const d={start_field:"start",end_field:"end",track_label_field:"state_name",track_split_field:"state_id",track_split_order:"DESC",track_split_legend_to_y_axis:2,split_tracks:!0,track_height:15,track_vertical_spacing:3,bounding_box_padding:2,always_hide_legend:!1,color:"#B8B8B8",fill_opacity:1,tooltip_positioning:"vertical"},g=t.DataLayers.get("BaseDataLayer");const _={closable:!1,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"{{intervals:state_name|htmlescape}}
          {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}"},h={namespace:{intervals:"intervals"},id:"intervals",type:"intervals",tag:"intervals",id_field:"{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}",start_field:"intervals:start",end_field:"intervals:end",track_split_field:"intervals:state_name",track_label_field:"intervals:state_name",split_tracks:!1,always_hide_legend:!0,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:state_name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],legend:[],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:_},u=t.Layouts.merge({id_field:"{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}",start_field:"intervals:chromStart",end_field:"intervals:chromEnd",track_split_field:"intervals:name",track_label_field:"intervals:name",split_tracks:!0,always_hide_legend:!1,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],tooltip:t.Layouts.merge({html:"Group: {{intervals:name|htmlescape}}
          \nRegion: {{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}}\n{{#if intervals:score}}
          \nScore: {{intervals:score|htmlescape}}{{/if}}"},_)},h),p={id:"intervals",tag:"intervals",min_height:50,height:50,margin:{top:25,right:150,bottom:5,left:70},toolbar:function(){const e=t.Layouts.get("toolbar","standard_panel");return e.widgets.push({type:"toggle_split_tracks",data_layer_id:"intervals",position:"right"}),e}(),axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},legend:{hidden:!0,orientation:"horizontal",origin:{x:50,y:0},pad_from_bottom:5},data_layers:[h]},y=t.Layouts.merge({min_height:120,height:120,data_layers:[u]},p),b={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association"),panels:[t.Layouts.get("panel","association"),t.Layouts.merge({min_height:120,height:120},p),t.Layouts.get("panel","genes")]};t.Adapters.add("IntervalLZ",class extends e{_getURL(t){const e=`?filter=id in ${this._config.source} and chromosome eq '${t.chr}' and start le ${t.end} and end ge ${t.start}`;return`${super._getURL(t)}${e}`}}),t.DataLayers.add("intervals",class extends g{constructor(e){t.Layouts.merge(e,d),super(...arguments),this._previous_categories=[],this._categories=[]}initialize(){super.initialize(),this._statusnodes_group=this.svg.group.append("g").attr("class","lz-data-layer-intervals lz-data-layer-intervals-statusnode"),this._datanodes_group=this.svg.group.append("g").attr("class","lz-data_layer-intervals")}_arrangeTrackSplit(t){const{track_split_field:e}=this.layout,a={};return t.forEach((t=>{const r=t[e];Object.prototype.hasOwnProperty.call(a,r)||(a[r]=[]),a[r].push(t)})),a}_arrangeTracksLinear(t,e=!0){if(e)return[t];const{start_field:a,end_field:r}=this.layout,s=[[]];return t.forEach(((t,e)=>{for(let e=0;e{d[t].forEach((t=>{t[r]=e(t[a]),t[i]=e(t[n]),t[s]=g*this.getTrackHeight()+o,t[l]=t[s]+c,t.track=g}))})),[g,Object.values(d).reduce(((t,e)=>t.concat(e)),[])]}getElementStatusNodeId(t){if(this.layout.split_tracks){const e="object"==typeof t?t.track:t;return`${this.getBaseId()}-statusnode-${e}`.replace(/[^\w]/g,"_")}return null}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}_applyLayoutOptions(){const t=this,e=this._base_layout,a=this.layout,r=e.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function})),s=a.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function}));if(!r)throw new Error("Interval tracks must define a `categorical_bin` color scale");const i=r.parameters.categories.length&&r.parameters.values.length,l=e.legend&&e.legend.length;if(!!i^!!l)throw new Error("To use a manually specified color scheme, both color and legend options must be set.");const n=e.color.find((function(t){return t.scale_function&&"to_rgb"===t.scale_function})),o=n&&n.field,c=this._generateCategoriesFromData(this.data,o);if(!i&&!l){const e=this._makeColorScheme(c);s.parameters.categories=c.map((function(t){return t[0]})),s.parameters.values=e,this.layout.legend=c.map((function(e,a){const r=e[0],i={shape:"rect",width:9,label:e[1],color:s.parameters.values[a]};return i[t.layout.track_split_field]=r,i}))}}render(){this._applyLayoutOptions(),this._previous_categories=this._categories;const[t,e]=this._assignTracks(this.data);this._categories=t;if(!t.every(((t,e)=>t===this._previous_categories[e])))return void this.updateSplitTrackAxis(t);const l=this._applyFilters(e);this._statusnodes_group.selectAll("rect").remove();const n=this._statusnodes_group.selectAll("rect").data(a.range(t.length));if(this.layout.split_tracks){const t=this.getTrackHeight();n.enter().append("rect").attr("class","lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared").attr("rx",this.layout.bounding_box_padding).attr("ry",this.layout.bounding_box_padding).merge(n).attr("id",(t=>this.getElementStatusNodeId(t))).attr("x",0).attr("y",(e=>e*t)).attr("width",this.parent.layout.cliparea.width).attr("height",Math.max(t-this.layout.track_vertical_spacing,1))}n.exit().remove();const o=this._datanodes_group.selectAll("rect").data(l,(t=>t[this.layout.id_field]));o.enter().append("rect").merge(o).attr("id",(t=>this.getElementId(t))).attr("x",(t=>t[r])).attr("y",(t=>t[s])).attr("width",(t=>Math.max(t[i]-t[r],1))).attr("height",this.layout.track_height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),o.exit().remove(),this._datanodes_group.call(this.applyBehaviors.bind(this)),this.parent&&this.parent.legend&&this.parent.legend.render()}_getTooltipPosition(t){return{x_min:t.data[r],x_max:t.data[i],y_min:t.data[s],y_max:t.data[l]}}updateSplitTrackAxis(t){const e=!!this.layout.track_split_legend_to_y_axis&&`y${this.layout.track_split_legend_to_y_axis}`;if(this.layout.split_tracks){const a=+t.length||0,r=+this.layout.track_height||0,s=2*(+this.layout.bounding_box_padding||0)+(+this.layout.track_vertical_spacing||0),i=a*r+(a-1)*s;this.parent.scaleHeightToData(i),e&&this.parent.legend&&(this.parent.legend.hide(),this.parent.layout.axes[e]={render:!0,ticks:[],range:{start:i-this.layout.track_height/2,end:this.layout.track_height/2}},this.layout.legend.forEach((r=>{const s=r[this.layout.track_split_field];let i=t.findIndex((t=>t===s));-1!==i&&("DESC"===this.layout.track_split_order&&(i=Math.abs(i-a-1)),this.parent.layout.axes[e].ticks.push({y:i-1,text:r.label}))})),this.layout.y_axis={axis:this.layout.track_split_legend_to_y_axis,floor:1,ceiling:a}),this.parent_plot.positionPanels()}else e&&this.parent.legend&&(this.layout.always_hide_legend||this.parent.legend.show(),this.parent.layout.axes[e]={render:!1},this.parent.render());return this}toggleSplitTracks(){return this.layout.split_tracks=!this.layout.split_tracks,this.parent.legend&&!this.layout.always_hide_legend&&(this.parent.layout.margin.bottom=5+(this.layout.split_tracks?0:this.parent.legend.layout.height+5)),this.render(),this}_makeColorScheme(t){if(t.find((t=>t[2])))return t.map((t=>c(0,t[2])));const e=t.length;return e<=15?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(233,150,122)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(50,205,50)","rgb(255,69,0)","rgb(255,0,0)"]:e<=18?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]:["rgb(212,212,212)","rgb(128,128,128)","rgb(112,48,160)","rgb(230,184,183)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,102)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,150,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]}_generateCategoriesFromData(t,e){const a=this,r=this._base_layout.legend;if(r&&r.length)return r.map((t=>[t[this.layout.track_split_field],t.label,t.color]));const s={},i=[];return t.forEach((t=>{const r=t[a.layout.track_split_field];Object.prototype.hasOwnProperty.call(s,r)||(s[r]=null,i.push([r,t[this.layout.track_label_field],t[e]]))})),i}}),t.Layouts.add("tooltip","standard_intervals",_),t.Layouts.add("data_layer","intervals",h),t.Layouts.add("data_layer","bed_intervals",u),t.Layouts.add("panel","intervals",p),t.Layouts.add("panel","bed_intervals",y),t.Layouts.add("plot","interval_association",b),t.ScaleFunctions.add("to_rgb",c),t.Widgets.add("toggle_split_tracks",class extends o{constructor(t){if(super(...arguments),t.data_layer_id||(t.data_layer_id="intervals"),!this.parent_panel.data_layers[t.data_layer_id])throw new Error("Toggle split tracks widget specifies an invalid data layer ID")}update(){const t=this.parent_panel.data_layers[this.layout.data_layer_id],e=t.layout.split_tracks?"Merge Tracks":"Split Tracks";return this.button?(this.button.setHtml(e),this.button.show(),this.parent.position(),this):(this.button=new n(this).setColor(this.layout.color).setHtml(e).setTitle("Toggle whether tracks are split apart or merged together").setOnclick((()=>{t.toggleSplitTracks(),this.scale_timeout&&clearTimeout(this.scale_timeout),this.scale_timeout=setTimeout((()=>{this.parent_panel.scaleHeightToData(),this.parent_plot.positionPanels()}),0),this.update()})),this.update())}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const o=n;LzIntervalsTrack=e.default})(); //# sourceMappingURL=lz-intervals-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-parsers.min.js b/dist/ext/lz-parsers.min.js index 63de381d..309d1025 100644 --- a/dist/ext/lz-parsers.min.js +++ b/dist/ext/lz-parsers.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.3 */ +/*! Locuszoom 0.14.0-beta.4 */ var LzParsers;(()=>{"use strict";var e={d:(r,t)=>{for(var l in t)e.o(t,l)&&!e.o(r,l)&&Object.defineProperty(r,l,{enumerable:!0,get:t[l]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r)},r={};e.d(r,{default:()=>d});const t=new Set(["",".","NA","N/A","n/a","nan","-nan","NaN","-NaN","null","NULL","None",null]),l=/([\d.-]+)([\sxeE]*)([0-9-]*)/;function n(e){return Number.isInteger(e)}function o(e,r=t,l=null){return e.map((e=>r.has(e)?l:e))}function a(e){return e.replace(/^chr/g,"").toUpperCase()}function s(e,r=!1){if(null===e)return e;const t=+e;if(r)return t;if(t<0||t>1)throw new Error("p value is not in the allowed range");if(0===t){if("0"===e)return 1/0;let[,r,,t]=e.match(l);return r=+r,t=""!==t?+t:0,0===r?1/0:-(Math.log10(+r)+ +t)}return-Math.log10(t)}function u(e){return null==e||"."===e?null:e}function i(e){return(e=u(e))?+e:null}const c=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function f(e,r=!1){const t=e&&e.match(c);if(t)return t.slice(1);if(r)return null;throw new Error(`Could not understand marker format for ${e}. Should be of format chr:pos or chr:pos_ref/alt`)}function p(e){const r=f(e);if(!r)throw new Error(`Unable to normalize marker format for variant: ${e}`);const[t,l,n,o]=r;let a=`${t}:${l}`;return n&&o&&(a+=`_${n}/${o}`),a}function _(e,r){const t=Array(r.length+1).fill(null).map((()=>Array(e.length+1).fill(null)));for(let r=0;r<=e.length;r+=1)t[0][r]=r;for(let e=0;e<=r.length;e+=1)t[e][0]=e;for(let l=1;l<=r.length;l+=1)for(let n=1;n<=e.length;n+=1){const o=e[n-1]===r[l-1]?0:1;t[l][n]=Math.min(t[l][n-1]+1,t[l-1][n]+1,t[l-1][n-1]+o)}return t[r.length][e.length]}function m(e,r,t=2){let l=t+1,n=null;for(let t=0;t_(o,e))));ae.variant1===r.ld_refvar))}}e.Adapters.add("UserTabixLD",l)}}"undefined"!=typeof LocusZoom&&LocusZoom.use(h);const d={install:h,makeBed12Parser:function({normalize:e=!0}={}){return r=>{const t=r.trim().split("\t");let[l,n,o,s,c,f,p,_,m,h,d,v]=t;if(!(l&&n&&o))throw new Error("Sample data must provide all required BED columns");if(f=u(f),e&&(l=a(l),n=+n+1,o=+o,c=i(c),p=i(p),_=i(_),m=u(m),h=i(h),d=u(d),d=d?d.replace(/,$/,"").split(",").map((e=>+e)):null,v=u(v),v=v?v.replace(/,$/,"").split(",").map((e=>+e+1)):null,d&&v&&h&&(d.length!==h||v.length!==h)))throw new Error("Block size and start information should provide the same number of items as in blockCount");return{chrom:l,chromStart:n,chromEnd:o,name:s,score:c,strand:f,thickStart:p,thickEnd:_,itemRgb:m,blockCount:h,blockSizes:d,blockStarts:v}}},makeGWASParser:function({marker_col:e,chrom_col:r,pos_col:l,ref_col:o,alt_col:u,pvalue_col:i,is_neg_log_pvalue:c=!1,rsid_col:p,beta_col:_,stderr_beta_col:m,allele_freq_col:h,allele_count_col:d,n_samples_col:v,is_alt_effect:g=!0,delimiter:w="\t"}){if(n(e)&&n(r)&&n(l))throw new Error("Must specify either marker OR chr + pos");if(!(n(e)||n(r)&&n(l)))throw new Error("Must specify how to locate marker");if(n(d)&&n(h))throw new Error("Allele count and frequency options are mutually exclusive");if(n(d)&&!n(v))throw new Error("To calculate allele frequency from counts, you must also provide n_samples");return b=>{const k=b.split(w);let y,E,q,A,N,$,S,L=null,z=null,P=null,C=null;if(n(e))[y,E,q,A]=f(k[e-1],!1);else{if(!n(r)||!n(l))throw new Error("Must specify all fields required to identify the variant");y=k[r-1],E=k[l-1]}if(y=a(y),y.startsWith("RS"))throw new Error(`Invalid chromosome specified: value "${y}" is an rsID`);n(o)&&(q=k[o-1]),n(u)&&(A=k[u-1]),n(p)&&(L=k[p-1]),t.has(q)&&(q=null),t.has(A)&&(A=null),t.has(L)?L=null:L&&(L=L.toLowerCase(),L.startsWith("rs")||(L=`rs${L}`));const O=s(k[i-1],c);q=q||null,A=A||null,n(h)&&(N=k[h-1]),n(d)&&($=k[d-1],S=k[v-1]),n(_)&&(z=k[_-1],z=t.has(z)?null:+z),n(m)&&(P=k[m-1],P=t.has(P)?null:+P),(h||d)&&(C=function({freq:e,allele_count:r,n_samples:l,is_alt_effect:n=!0}){if(void 0!==e&&void 0!==r)throw new Error("Frequency and allele count options are mutually exclusive");let o;if(void 0===e&&(t.has(r)||t.has(l)))return null;if(void 0===e&&void 0!==r)o=+r/+l/2;else{if(t.has(e))return null;o=+e}if(o<0||o>1)throw new Error("Allele frequency is not in the allowed range");return n?o:1-o}({freq:N,allele_count:$,n_samples:S,is_alt_effect:g}));const R=q&&A?`_${q}/${A}`:"";return{chromosome:y,position:+E,ref_allele:q?q.toUpperCase():null,alt_allele:A?A.toUpperCase():null,variant:`${y}:${E}${R}`,rsid:L,log_pvalue:O,beta:z,stderr_beta:P,alt_allele_freq:C}}},guessGWAS:function(e,r,t=1){const l=e.map((e=>e?e.toLowerCase():e));l[0].replace("/^#+/","");const n=function(e,r){let t;const l=(e,r,l)=>{const n=o(r.map((r=>r[e])));try{t=n.map((e=>s(e,l)))}catch(e){return!1}return t.every((e=>!Number.isNaN(e)))},n=m(["neg_log_pvalue","log_pvalue","log_pval","logpvalue"],e),a=m(["pvalue","p.value","p-value","pval","p_score","p","p_value"],e);return null!==n&&l(n,r,!0)?{pvalue_col:n+1,is_neg_log_pvalue:!0}:a&&l(a,r,!1)?{pvalue_col:a+1,is_neg_log_pvalue:!1}:null}(l,r);if(!n)return null;l[n.pvalue_col-1]=null;const a=function(e,r){const t=r[0];let l=m(["snpid","marker","markerid","snpmarker","chr:position"],e);if(null!==l&&f(t[l],!0))return l+=1,{marker_col:l};const n=e.slice(),o=[["chrom_col",["chrom","chr","chromosome"],!0],["pos_col",["position","pos","begin","beg","bp","end","ps","base_pair_location"],!0],["ref_col",["A1","ref","reference","allele0","allele1"],!1],["alt_col",["A2","alt","alternate","allele1","allele2"],!1]],a={};for(let e=0;e{l[a[e]]=null}));const u=function(e,r){function t(e,r){const t=o(r.map((r=>r[e])));let l;try{l=t.filter((e=>null!==e)).map((e=>+e))}catch(e){return!1}return l.every((e=>!Number.isNaN(e)))}const l=m(["beta","effect_size","alt_effsize","effect"],e,0),n=m(["stderr_beta","stderr","sebeta","effect_size_sd","se","standard_error"],e,0),a={};return null!==l&&t(l,r)&&(a.beta_col=l+1),null!==n&&t(n,r)&&(a.stderr_beta_col=n+1),a}(l,r);return n&&a?Object.assign({},n,a,u||{}):null},makePlinkLdParser:function({normalize:e=!0}={}){return r=>{let[t,l,n,o,s,u,i]=r.trim().split("\t");return e&&(t=a(t),o=a(o),n=p(n),u=p(u),l=+l,s=+s,i=+i),{chromosome1:t,position1:l,variant1:n,chromosome2:o,position2:s,variant2:u,correlation:i}}}};LzParsers=r.default})(); //# sourceMappingURL=lz-parsers.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-tabix-source.min.js b/dist/ext/lz-tabix-source.min.js index 7700b1d2..54314cf3 100644 --- a/dist/ext/lz-tabix-source.min.js +++ b/dist/ext/lz-tabix-source.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.3 */ +/*! Locuszoom 0.14.0-beta.4 */ var LzTabix;(()=>{var t={398:t=>{var i=15,e=-2,n=-3,r=-5,s=13,a=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],o=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],h=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],l=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],f=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],u=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],d=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];function _(){}function c(){this.was=[0]}_.prototype.inflateInit=function(t,i){return t||(t=15),i&&(i=!1),this.istate=new c,this.istate.inflateInit(this,i?-t:t)},_.prototype.inflate=function(t){return null==this.istate?e:this.istate.inflate(this,t)},_.prototype.inflateEnd=function(){if(null==this.istate)return e;var t=istate.inflateEnd(this);return this.istate=null,t},_.prototype.inflateSync=function(){return istate.inflateSync(this)},_.prototype.inflateSetDictionary=function(t,i){return istate.inflateSetDictionary(this,t,i)},c.prototype.inflateReset=function(t){return null==t||null==t.istate?e:(t.total_in=t.total_out=0,t.msg=null,t.istate.mode=0!=t.istate.nowrap?7:0,t.istate.blocks.reset(t,null),0)},c.prototype.inflateEnd=function(t){return null!=this.blocks&&this.blocks.free(t),this.blocks=null,0},c.prototype.inflateInit=function(t,i){return t.msg=null,this.blocks=null,nowrap=0,i<0&&(i=-i,nowrap=1),i<8||i>15?(this.inflateEnd(t),e):(this.wbits=i,t.istate.blocks=new v(t,0!=t.istate.nowrap?null:this,1<>4)>t.istate.wbits){t.istate.mode=s,t.msg="invalid window size",t.istate.marker=5;break}t.istate.mode=1;case 1:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,o=255&t.next_in[t.next_in_index++],((t.istate.method<<8)+o)%31!=0){t.istate.mode=s,t.msg="incorrect header check",t.istate.marker=5;break}if(0==(32&o)){t.istate.mode=7;break}t.istate.mode=2;case 2:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=3;case 3:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=4;case 4:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=5;case 5:return 0==t.avail_in?a:(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.adler=t.istate.need,t.istate.mode=6,2);case 6:return t.istate.mode=s,t.msg="need dictionary",t.istate.marker=0,e;case 7:if((a=t.istate.blocks.proc(t,a))==n){t.istate.mode=s,t.istate.marker=0;break}if(0==a&&(a=i),1!=a)return a;if(a=i,t.istate.blocks.reset(t,t.istate.was),0!=t.istate.nowrap){t.istate.mode=12;break}t.istate.mode=8;case 8:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=9;case 9:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=10;case 10:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=11;case 11:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.istate.was[0]!=t.istate.need){t.istate.mode=s,t.msg="incorrect data check",t.istate.marker=5;break}t.istate.mode=12;case 12:return 1;case s:return n;default:return e}},c.prototype.inflateSetDictionary=function(t,i,r){var s=0,a=r;return null==t||null==t.istate||6!=t.istate.mode?e:t._adler.adler32(1,i,0,r)!=t.adler?n:(t.adler=t._adler.adler32(0,null,0,0),a>=1<>>1){case 0:o>>>=3,o>>>=r=7&(h-=3),h-=r,this.mode=1;break;case 1:w(v=new Int32Array(1),p=new Int32Array(1),m=[],y=[],t),this.codes.init(v[0],p[0],m[0],0,y[0],0,t),o>>>=3,h-=3,this.mode=6;break;case 2:o>>>=3,h-=3,this.mode=3;break;case 3:return o>>>=3,h-=3,this.mode=s,t.msg="invalid block type",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i)}break;case 1:for(;h<32;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>16&65535)!=(65535&o))return this.mode=s,t.msg="invalid stored block lengths",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.left=65535&o,o=h=0,this.mode=0!=this.left?2:0!=this.last?7:0;break;case 2:if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);if(0==d&&(u==end&&0!=read&&(d=(u=0)f&&(r=f),r>d&&(r=d),g(t.next_in,l,this.window,u,r),l+=r,f-=r,u+=r,d-=r,0!=(this.left-=r))break;this.mode=0!=this.last?7:0;break;case 3:for(;h<14;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<29||(r>>5&31)>29)return this.mode=9,t.msg="too many length or distance symbols",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);if(r=258+(31&r)+(r>>5&31),null==this.blens||this.blens.length>>=14,h-=14,this.index=0,mode=4;case 4:for(;this.index<4+(this.table>>>10);){for(;h<3;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>=3,h-=3}for(;this.index<19;)this.blens[b[this.index++]]=0;if(this.bb[0]=7,0!=(r=this.inftree.inflate_trees_bits(this.blens,this.bb,this.tb,this.hufts,t)))return(i=r)==n&&(this.blens=null,this.mode=9),this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);this.index=0,this.mode=5;case 5:for(;r=this.table,this.index<258+(31&r)+(r>>5&31);){var c,x;for(r=this.bb[0];h>>=r,h-=r,this.blens[this.index++]=x;else{for(_=18==x?7:x-14,c=18==x?11:3;h>>=r)&a[_],o>>>=_,h-=_,(_=this.index)+c>258+(31&(r=this.table))+(r>>5&31)||16==x&&_<1)return this.blens=null,this.mode=9,t.msg="invalid bit length repeat",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);x=16==x?this.blens[_-1]:0;do{this.blens[_++]=x}while(0!=--c);this.index=_}}this.tb[0]=-1;var v=new Int32Array(1),p=new Int32Array(1),m=new Int32Array(1),y=new Int32Array(1);if(v[0]=9,p[0]=6,r=this.table,0!=(r=this.inftree.inflate_trees_dynamic(257+(31&r),1+(r>>5&31),this.blens,v,p,m,y,this.hufts,t)))return r==n&&(this.blens=null,this.mode=s),i=r,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.codes.init(v[0],p[0],this.hufts,m[0],this.hufts,y[0],t),this.mode=6;case 6:if(this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,1!=(i=this.codes.proc(this,t,i)))return this.inflate_flush(t,i);if(i=0,this.codes.free(t),l=t.next_in_index,f=t.avail_in,o=this.bitb,h=this.bitk,d=(u=this.write)t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,(s+=e)==this.end&&(s=0,this.write==this.end&&(this.write=0),(e=this.write-s)>t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,s+=e),t.next_out_index=n,this.read=s,i};function p(){}function m(){}function w(t,i,e,n,r){return t[0]=9,i[0]=5,e[0]=o,n[0]=h,0}p.prototype.init=function(t,i,e,n,r,s,a){this.mode=0,this.lbits=t,this.dbits=i,this.ltree=e,this.ltree_index=n,this.dtree=r,this.dtree_index=s,this.tree=null},p.prototype.proc=function(t,i,r){var s,o,h,l,f,u,d,_=0,c=0,x=0;for(x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)=258&&l>=10&&(t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,r=this.inflate_fast(this.lbits,this.dbits,this.ltree,this.ltree_index,this.dtree,this.dtree_index,t,i),x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)>>=this.tree[o+1],c-=this.tree[o+1],0==(h=this.tree[o])){this.lit=this.tree[o+2],this.mode=6;break}if(0!=(16&h)){this.get=15&h,this.len=this.tree[o+2],this.mode=2;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}if(0!=(32&h)){this.mode=7;break}return this.mode=9,i.msg="invalid literal/length code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 2:for(s=this.get;c>=s,c-=s,this.need=this.dbits,this.tree=this.dtree,this.tree_index=this.dtree_index,this.mode=3;case 3:for(s=this.need;c>=this.tree[o+1],c-=this.tree[o+1],0!=(16&(h=this.tree[o]))){this.get=15&h,this.dist=this.tree[o+2],this.mode=4;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}return this.mode=9,i.msg="invalid distance code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 4:for(s=this.get;c>=s,c-=s,this.mode=5;case 5:for(d=f-this.dist;d<0;)d+=t.end;for(;0!=this.len;){if(0==u&&(f==t.end&&0!=t.read&&(u=(f=0)7&&(c-=8,l++,x--),t.write=f,r=t.inflate_flush(i,r),u=(f=t.write)>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15,k=u[S+2]+(c&a[_]),c>>=_,x-=_;x<15;)v--,c|=(255&l.next_in[b++])<>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15;x<_;)v--,c|=(255&l.next_in[b++])<>=_,x-=_,m-=k,p>=A)C=p-A,h.window[p++]=h.window[C++],h.window[p++]=h.window[C++],k-=2;else{C=p-A;do{C+=h.end}while(C<0);if(k>(_=h.end-C)){if(k-=_,p-C>0&&_>p-C)do{h.window[p++]=h.window[C++]}while(0!=--_);else g(h.window,C,h.window,p,_),p+=_,C+=_,_=0;C=0}}do{h.window[p++]=h.window[C++]}while(0!=--k);break}if(0!=(64&_))return l.msg="invalid distance code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n;f+=u[S+2],_=u[S=3*(d+(f+=c&a[_]))]}break}if(0!=(64&_))return 0!=(32&_)?(v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,1):(l.msg="invalid literal/length code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n);if(f+=u[S+2],0==(_=u[S=3*(d+(f+=c&a[_]))])){c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--;break}}else c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--}while(m>=258&&v>=10);return v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,0},m.prototype.huft_build=function(t,e,s,a,o,h,l,f,u,d,_){var c,x,b,v,p,m,w,y,k,A,C,S,R,I,L;A=0,p=s;do{this.c[t[e+A]]++,A++,p--}while(0!=p);if(this.c[0]==s)return l[0]=-1,f[0]=0,0;for(y=f[0],m=1;m<=i&&0==this.c[m];m++);for(w=m,yp&&(y=p),f[0]=y,I=1<S+y;){if(v++,L=(L=b-(S+=y))>y?y:L,(x=1<<(m=w-S))>c+1&&(x-=c+1,R=w,m1440)return n;this.u[v]=C=this.hn[0],this.hn[0]+=L,0!=v?(this.x[v]=p,this.r[0]=m,this.r[1]=y,m=p>>>S-y,this.r[2]=C-this.u[v-1]-m,g(this.r,0,u,3*(this.u[v-1]+m),3)):l[0]=C}for(this.r[1]=w-S,A>=s?this.r[0]=192:_[A]>>S;m>>=1)p^=m;for(p^=m,k=(1<257?(x==n?c.msg="oversubscribed distance tree":x==r?(c.msg="incomplete distance tree",x=n):-4!=x&&(c.msg="empty distance tree with lengths",x=n),x):0)},m.prototype.initWorkArea=function(t){null==this.hn&&(this.hn=new Int32Array(1),this.v=new Int32Array(t),this.c=new Int32Array(16),this.r=new Int32Array(3),this.u=new Int32Array(i),this.x=new Int32Array(16)),this.v.length100?k(new Uint8Array(t.buffer,t.byteOffset+i,r),e,n):function(t,i,e,n,r){for(var s=0;s{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},e.o=(t,i)=>Object.prototype.hasOwnProperty.call(t,i);var n={};(()=>{"use strict";e.d(n,{default:()=>q});function t(t){return r(i(s(t)))}function i(t){return o(h(a(t),8*t.length))}function r(t){for(var i="",e=t.length,n=0;n8*t.length?i+="":i+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(r>>>6*(3-s)&63);return i}function s(t){for(var i,e,n="",r=-1;++r>>6&31,128|63&i):i<=65535?n+=String.fromCharCode(224|i>>>12&15,128|i>>>6&63,128|63&i):i<=2097151&&(n+=String.fromCharCode(240|i>>>18&7,128|i>>>12&63,128|i>>>6&63,128|63&i));return n}function a(t){for(var i=Array(t.length>>2),e=0;e>5]|=(255&t.charCodeAt(e/8))<<24-e%32;return i}function o(t){for(var i="",e=0;e<32*t.length;e+=8)i+=String.fromCharCode(t[e>>5]>>>24-e%32&255);return i}function h(t,i){t[i>>5]|=128<<24-i%32,t[15+(i+64>>9<<4)]=i;for(var e=Array(80),n=1732584193,r=-271733879,s=-1732584194,a=271733878,o=-1009589776,h=0;h>16)+(i>>16)+(e>>16)<<16|65535&e}function d(t,i){return t<>>32-i}function _(t){this.value=t,this.listeners=[]}function c(){this.queue=[]}_.prototype.addListener=function(t){this.listeners.push(t)},_.prototype.addListenerAndFire=function(t){this.listeners.push(t),t(this.value)},_.prototype.removeListener=function(t){var i,e;i=this.listeners,(e=function(t,i){if(!t)return-1;for(var e=0;e=0&&i.splice(e,1)},_.prototype.get=function(){return this.value},_.prototype.set=function(t){this.value=t;for(var i=0;i=0&&navigator.userAgent.indexOf("Chrome")<0;function m(t){if(!t)return null;for(var i=new Uint8Array(t.length),e=0;e1e8)throw"Monster fetch!";r.setRequestHeader("Range","bytes="+e.start+"-"+e.end),e.end-e.start+1}r.onreadystatechange=function(){if(4==r.readyState)return 200==r.status||206==r.status?i(r.responseText):i(null)},e.opts.credentials&&(r.withCredentials=!0),r.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))},b.prototype.salted=function(){var t=function(t){var i={};for(var e in t)i[e]=t[e];return i}(this.opts);return t.salt=!0,new b(this.url,this.start,this.end,t)},b.prototype.getURL=function(){return this.opts.resolver?this.opts.resolver(this.url).then((function(t){return"string"==typeof t?t:t.url})):Promise.resolve(this.url)},b.prototype.fetch=function(i,e){var n=this,r=(e=e||{}).attempt||1,s=e.truncatedLength;if(r>3)return i(null);this.getURL().then((function(a){try{var o;e.timeout&&!n.opts.credentials&&(o=setTimeout((function(){return console.log("timing out "+a),l.abort(),i(null,"Timeout")}),e.timeout));var h,l=new XMLHttpRequest;if((p||n.opts.salt)&&a.indexOf("?")<0&&(a=a+"?salt="+t(Date.now()+","+ ++v)),l.open("GET",a,!0),l.overrideMimeType("text/plain; charset=x-user-defined"),n.end){if(n.end-n.start>1e8)throw"Monster fetch!";l.setRequestHeader("Range","bytes="+n.start+"-"+n.end),h=n.end-n.start+1}l.responseType="arraybuffer",l.onreadystatechange=function(){if(4==l.readyState){if(o&&clearTimeout(o),200==l.status||206==l.status){if(l.response){var t=l.response.byteLength;return!h||h==t||s&&t==s?i(l.response):n.fetch(i,{attempt:r+1,truncatedLength:t})}if(l.mozResponseArrayBuffer)return i(l.mozResponseArrayBuffer);var e=l.responseText;return!h||h==e.length||s&&e.length==s?i(m(l.responseText)):n.fetch(i,{attempt:r+1,truncatedLength:e.length})}return n.fetch(i,{attempt:r+1})}},n.opts.credentials&&(l.withCredentials=!0),l.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))};var w=new ArrayBuffer(8);new Uint8Array(w),new Float32Array(w);function y(t,i){return t[i+3]<<24|t[i+2]<<16|t[i+1]<<8|t[i]}var g=e(398);function k(t,i){this.block=t,this.offset=i}function A(t,i,e){var n=4294967296*(255&t[i+6])+16777216*(255&t[i+5])+65536*(255&t[i+4])+256*(255&t[i+3])+(255&t[i+2]),r=t[i+1]<<8|t[i];return 0!=n||0!=r||e?new k(n,r):null}function C(t,i){i=Math.min(i||1,t.byteLength-50);for(var e=[],n=[0],r=0;n[0]n._max&&(n._max=t._max):(e.push(n),n=t)})),e.push(n),this._ranges=e}function L(t,i){return t._mini._min?1:t._maxt._max?1:0}R.prototype.min=function(){return this._min},R.prototype.max=function(){return this._max},R.prototype.contains=function(t){return t>=this._min&&t<=this._max},R.prototype.isContiguous=function(){return!0},R.prototype.ranges=function(){return[this]},R.prototype._pushRanges=function(t){t.push(this)},R.prototype.toString=function(){return"["+this._min+"-"+this._max+"]"},I.prototype.min=function(){return this._ranges[0].min()},I.prototype.max=function(){return this._ranges[this._ranges.length-1].max()},I.prototype.lower_bound=function(t){var i=this.ranges();if(t>this.max())return i.length;if(ti[r]._max)e=r+1;else{if(!(tt._max&&(t._max=e[n]._max),this._ranges.splice(i,n-i+1,t)}}else this._ranges.push(t)},I.prototype.isContiguous=function(){return this._ranges.length>1},I.prototype.ranges=function(){return this._ranges},I.prototype._pushRanges=function(t){for(var i=0;i0&&(t+=","),t+=this._ranges[i].toString();return t};function T(){}function U(t,i){return new Promise((function(e,n){var r,s,a,o;r=new b(t),s=new b(i),a=function(t,i){i?n(i):e(t)},(o=new T).data=r,o.tbi=s,o.tbi.fetch((function(t){if(!t)return a(null,"Couldn't access Tabix");var i=C(t,t.byteLength),e=new Uint8Array(i);if(21578324!=y(e,0))return a(null,"Not a tabix index");var n=y(e,4);o.format=y(e,8),o.colSeq=y(e,12),o.colStart=y(e,16),o.colEnd=y(e,20),o.meta=y(e,24),o.skip=y(e,28),y(e,32),o.indices=[];var r=36;o.chrToIndex={},o.indexToChr=[];for(var s=0;s0&&(p+=65536),p0&&(o.indices[u]=new Uint8Array(i,d,r-d))}o.headerMax=f,a(o)}),{timeout:5e4})}))}function E(t){const i=t.Adapters.get("BaseLZAdapter");t.Adapters.add("TabixUrlSource",class extends i{constructor(t){if(super(t),!t.parser_func||!t.url_data&&!t.reader)throw new Error("Tabix source is missing required configuration options");if(this.parser=t.parser_func,this.url_data=t.url_data,this.url_tbi=t.url_tbi||`${this.url_data}.tbi`,this._overfetch=t.overfetch||0,this._overfetch<0||this._overfetch>1)throw new Error("Overfetch must be specified as a fraction (0-1) of the requested region size");this.url_data?this._reader_promise=U(this.url_data,this.url_tbi).catch((function(){throw new Error("Failed to create a tabix reader from the provided URL")})):this._reader_promise=Promise.resolve(t.reader)}_performRequest(t){return new Promise(((i,e)=>{const n=t.start,r=t.end,s=this._overfetch*(r-n),a=t.start-s,o=t.end+s;this._reader_promise.then((n=>{n.fetch(t.chr,a,o,(function(t,n){n&&e(new Error("Could not read requested region. This may indicate an error with the .tbi index.")),i(t)}))}))}))}_normalizeResponse(t){return t.map(this.parser)}})}T.prototype.blocksForRange=function(t,i,e){var n=this.indices[t];if(!n)return[];for(var r=function(t,i){var e,n=[];for(--i,n.push(0),e=1+(t>>26);e<=1+(i>>26);++e)n.push(e);for(e=9+(t>>23);e<=9+(i>>23);++e)n.push(e);for(e=73+(t>>20);e<=73+(i>>20);++e)n.push(e);for(e=585+(t>>17);e<=585+(i>>17);++e)n.push(e);for(e=4681+(t>>14);e<=4681+(i>>14);++e)n.push(e);return n}(i,e),s=[],a=0;a>14,v-1),w=Math.min(e>>14,v-1);for(a=m;a<=w;++a){var g=A(n,f+4+8*a);g&&((!p||g.block=p.block&&C.maxv.offset>=p.offset&&k.push(C)}h=k;var R=[];for(a=0;a0){var L=R[0];for(a=1;a=a.length)return n(l);if(h){var s=new Uint8Array(h);return r.readRecords(s,a[f].minv.offset,l,i,e,o),h=null,++f,t()}var u=a[f],d=u.minv.block,_=u.maxv.block+65536;r.data.slice(d,_-d).fetch((function(i){try{return h=C(i,u.maxv.block-u.minv.block+1),t()}catch(t){return n(null,t)}}))}()}catch(t){n(null,t)}},T.prototype.readRecords=function(t,i,e,n,r,s){t:for(;;){for(var a="";i0&&(f=parseInt(h[this.colEnd-1])),65536&this.format&&++l,l<=r&&f>=n&&e.push(a)}continue t}a+=String.fromCharCode(o)}return}},T.prototype.fetchHeader=function(t,i){var e={metaOnly:!0,skipped:!1,nLines:0};i=i||e,Object.keys(e).forEach((function(t){i.hasOwnProperty(t)||(i[t]=e[t])}));var n=this;n.data.slice(0,n.headerMax).fetch((function(e){if(!e)return t(null,"Fetch failed");for(var r=new Uint8Array(C(e,e.byteLength)),s=0,a="",o=[];s{"use strict";var t={d:(e,o)=>{for(var a in o)t.o(o,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:o[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>i});d3;Math.sqrt(3);function o(t){return JSON.parse(JSON.stringify(t))}const a=["highlight","select","fade","hide"],l=["highlighted","selected","faded","hidden"],s=["unhighlight","deselect","unfade","show"];function n(t){const e=t.Widgets.get("_Button"),n=t.Widgets.get("BaseWidget");const i=function(){const e=t.Layouts.get("tooltip","standard_association");return e.html+='Condition on Variant
          ',e}(),r=function(){const e=t.Layouts.get("toolbar","standard_association");return e.widgets.push({type:"covariates_model",button_html:"Model",button_title:"Show and edit covariates currently in model",position:"left"}),e}();t.Widgets.add("covariates_model",class extends n{initialize(){this.parent_plot.state.model=this.parent_plot.state.model||{},this.parent_plot.state.model.covariates=this.parent_plot.state.model.covariates||[],this.parent_plot.CovariatesModel={button:this,add:t=>{const e=this.parent_plot,a=o(t);"object"==typeof t&&"string"!=typeof a.html&&(a.html="function"==typeof t.toHTML?t.toHTML():t.toString());for(let t=0;t{const e=this.parent_plot;if(void 0===e.state.model.covariates[t])throw new Error(`Unable to remove model covariate, invalid index: ${t.toString()}`);return e.state.model.covariates.splice(t,1),e.applyState(),e.CovariatesModel.updateWidget(),e},removeAll:()=>{const t=this.parent_plot;return t.state.model.covariates=[],t.applyState(),t.CovariatesModel.updateWidget(),t},updateWidget:()=>{this.button.update(),this.button.menu.update()}}}update(){return this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=this.button.menu.inner_selector;if(t.html(""),void 0!==this.parent_plot.state.model.html&&t.append("div").html(this.parent_plot.state.model.html),this.parent_plot.state.model.covariates.length){t.append("h5").html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);const e=t.append("table");this.parent_plot.state.model.covariates.forEach(((t,o)=>{const a="object"==typeof t&&"string"==typeof t.html?t.html:t.toString(),l=e.append("tr");l.append("td").append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","0em").on("click",(()=>this.parent_plot.CovariatesModel.removeByIdx(o))).html("×"),l.append("td").html(a)})),t.append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","4px").html("× Remove All Covariates").on("click",(()=>this.parent_plot.CovariatesModel.removeAll()))}else t.append("i").html("no covariates in model")})),this.button.preUpdate=()=>{let t="Model";const e=this.parent_plot.state.model.covariates.length;if(e){t+=` (${e} ${e>1?"covariates":"covariate"})`}this.button.setHtml(t).disable(!1)},this.button.show()),this}}),t.Widgets.add("data_layers",class extends n{update(){return"string"!=typeof this.layout.button_html&&(this.layout.button_html="Data Layers"),"string"!=typeof this.layout.button_title&&(this.layout.button_title="Manipulate Data Layers (sort, dim, show/hide, etc.)"),this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html("");const t=this.button.menu.inner_selector.append("table");return this.parent_panel._data_layer_ids_by_z_index.slice().reverse().forEach(((e,o)=>{const n=this.parent_panel.data_layers[e],i="string"!=typeof n.layout.name?n.id:n.layout.name,r=t.append("tr");r.append("td").html(i),this.layout.statuses.forEach((t=>{const e=l.indexOf(t),o=a[e];let i,d,u;n._global_statuses[t]?(i=s[e],d=`un${o}AllElements`,u="-highlighted"):(i=a[e],d=`${o}AllElements`,u=""),r.append("td").append("a").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}${u}`).style("margin-left","0em").on("click",(()=>{n[d](),this.button.menu.populate()})).html(i)}));const d=0===o,u=o===this.parent_panel._data_layer_ids_by_z_index.length-1,p=r.append("td");p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${u?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveBack(),this.button.menu.populate()})).html("▾").attr("title","Move layer down (further back)"),p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${d?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveForward(),this.button.menu.populate()})).html("▴").attr("title","Move layer up (further front)"),p.append("a").attr("class","lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red").style("margin-left","0em").on("click",(()=>(confirm(`Are you sure you want to remove the ${i} layer? This cannot be undone.`)&&n.parent.removeDataLayer(e),this.button.menu.populate()))).html("×").attr("title","Remove layer")})),this})),this.button.show()),this}}),t.Layouts.add("tooltip","covariates_model_association",i),t.Layouts.add("toolbar","covariates_model_plot",r)}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const i=n;LzWidgetAddons=e.default})(); //# sourceMappingURL=lz-widget-addons.min.js.map \ No newline at end of file diff --git a/dist/locuszoom.app.min.js b/dist/locuszoom.app.min.js index e7e72466..3c78cc18 100644 --- a/dist/locuszoom.app.min.js +++ b/dist/locuszoom.app.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.3 */ -var LocusZoom;(()=>{var t={483:(t,e,s)=>{"use strict";const i=s(919);t.exports=function(t,...e){if(!t){if(1===e.length&&e[0]instanceof Error)throw e[0];throw new i(e)}}},919:(t,e,s)=>{"use strict";const i=s(820);t.exports=class extends Error{constructor(t){super(t.filter((t=>""!==t)).map((t=>"string"==typeof t?t:t instanceof Error?t.message:i(t))).join(" ")||"Unknown error"),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e.assert)}}},820:t=>{"use strict";t.exports=function(...t){try{return JSON.stringify.apply(null,t)}catch(t){return"[Cannot display object: "+t.message+"]"}}},681:(t,e,s)=>{"use strict";const i=s(483),a={};e.B=class{constructor(){this._items=[],this.nodes=[]}add(t,e){const s=[].concat((e=e||{}).before||[]),a=[].concat(e.after||[]),n=e.group||"?",o=e.sort||0;i(!s.includes(n),`Item cannot come before itself: ${n}`),i(!s.includes("?"),"Item cannot come before unassociated items"),i(!a.includes(n),`Item cannot come after itself: ${n}`),i(!a.includes("?"),"Item cannot come after unassociated items"),Array.isArray(t)||(t=[t]);for(const e of t){const t={seq:this._items.length,sort:o,before:s,after:a,group:n,node:e};this._items.push(t)}if(!e.manual){const t=this._sort();i(t,"item","?"!==n?`added into group ${n}`:"","created a dependencies error")}return this.nodes}merge(t){Array.isArray(t)||(t=[t]);for(const e of t)if(e)for(const t of e._items)this._items.push(Object.assign({},t));this._items.sort(a.mergeSort);for(let t=0;tt.sort===e.sort?0:t.sort{function e(t){if("string"==typeof t.source.flags)return t.source.flags;var e=[];return t.global&&e.push("g"),t.ignoreCase&&e.push("i"),t.multiline&&e.push("m"),t.sticky&&e.push("y"),t.unicode&&e.push("u"),e.join("")}t.exports=function t(s){if("function"==typeof s)return s;var i=Array.isArray(s)?[]:{};for(var a in s){var n=s[a],o={}.toString.call(n).slice(8,-1);i[a]="Array"==o||"Object"==o?t(n):"Date"==o?new Date(n.getTime()):"RegExp"==o?RegExp(n.source,e(n)):n}return i}}},e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={exports:{}};return t[i](a,a.exports,s),a.exports}s.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return s.d(e,{a:e}),e},s.d=(t,e)=>{for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),s.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";s.d(i,{default:()=>ds});var t={};s.r(t),s.d(t,{AssociationLZ:()=>M,BaseAdapter:()=>$,BaseApiAdapter:()=>z,BaseLZAdapter:()=>k,BaseUMAdapter:()=>E,GeneConstraintLZ:()=>A,GeneLZ:()=>N,GwasCatalogLZ:()=>S,LDServer:()=>O,PheWASLZ:()=>j,RecombLZ:()=>T,StaticSource:()=>L});var e={};s.r(e),s.d(e,{htmlescape:()=>F,is_numeric:()=>H,log10:()=>D,logtoscinotation:()=>U,neglog10:()=>B,scinotation:()=>q,urlencode:()=>G});var a={};s.r(a),s.d(a,{BaseWidget:()=>yt,_Button:()=>ft,display_options:()=>Ot,download:()=>vt,download_png:()=>wt,filter_field:()=>xt,menu:()=>St,move_panel_down:()=>kt,move_panel_up:()=>zt,region_scale:()=>bt,remove_panel:()=>$t,resize_to_data:()=>Nt,set_state:()=>Tt,shift_region:()=>Et,title:()=>mt,toggle_legend:()=>At,zoom_region:()=>Mt});var n={};s.r(n),s.d(n,{categorical_bin:()=>Vt,effect_direction:()=>Qt,if_value:()=>Zt,interpolate:()=>Xt,numerical_bin:()=>Kt,ordinal_cycle:()=>Wt,stable_choice:()=>Yt});var o={};s.r(o),s.d(o,{BaseDataLayer:()=>ie,annotation_track:()=>ne,arcs:()=>he,category_scatter:()=>me,genes:()=>de,highlight_regions:()=>re,line:()=>_e,orthogonal_line:()=>ge,scatter:()=>fe});var r={};s.r(r),s.d(r,{data_layer:()=>es,panel:()=>ss,plot:()=>is,toolbar:()=>ts,toolbar_widgets:()=>Qe,tooltip:()=>Xe});const l="0.14.0-beta.3";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}class d{constructor(t,e,s={},i=null,a=null){this.key=t,this.value=e,this.metadata=s,this.prev=i,this.next=a}}class u{constructor(t=3){if(this._max_size=t,this._cur_size=0,this._store=new Map,this._head=null,this._tail=null,null===t||t<0)throw new Error('Cache "max_size" must be >= 0')}has(t){return this._store.has(t)}get(t){const e=this._store.get(t);return e?(this._head!==e&&this.add(t,e.value),e.value):null}add(t,e,s={}){if(0===this._max_size)return;const i=this._store.get(t);i&&this._remove(i);const a=new d(t,e,s,null,this._head);if(this._head?this._head.prev=a:this._tail=a,this._head=a,this._store.set(t,a),this._max_size>=0&&this._cur_size>=this._max_size){const t=this._tail;this._tail=this._tail.prev,this._remove(t)}this._cur_size+=1}clear(){this._head=null,this._tail=null,this._cur_size=0,this._store=new Map}remove(t){const e=this._store.get(t);return!!e&&(this._remove(e),!0)}_remove(t){null!==t.prev?t.prev.next=t.next:this._head=t.next,null!==t.next?t.next.prev=t.prev:this._tail=t.prev,this._store.delete(t.key),this._cur_size-=1}find(t){let e=this._head;for(;e;){const s=e.next;if(t(e))return e;e=s}}}var _=s(957),p=s.n(_);function g(t){return"object"!=typeof t?t:p()(t)}var y=s(681);function f(t,e,s,i=!0){if(!s.length)return[];const a=s.map((t=>function(t){const e=/^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(t);if(!e)throw new Error(`Unable to parse dependency specification: ${t}`);let{name_alone:s,name_deps:i,deps:a}=e.groups;return s?[s,[]]:(a=a.split(/\s*,\s*/),[i,a])}(t))),n=new Map(a),o=new y.B;for(let[t,e]of n.entries())try{o.add(t,{after:e,group:t})}catch(e){throw new Error(`Invalid or possible circular dependency specification for: ${t}`)}const r=o.nodes,l=new Map;for(let s of r){const i=e.get(s);if(!i)throw new Error(`Data has been requested from source '${s}', but no matching source was provided`);const a=n.get(s)||[],o=Promise.all(a.map((t=>l.get(t)))).then((e=>{const a=Object.assign({_provider_name:s},t);return i.getData(a,...e)}));l.set(s,o)}return Promise.all([...l.values()]).then((t=>i?t[t.length-1]:t))}function m(t,e){const s=new Map;for(let i of t){const t=i[e];if(void 0===t)throw new Error(`All records must specify a value for the field "${e}"`);if("object"==typeof t)throw new Error("Attempted to group on a field with non-primitive values");let a=s.get(t);a||(a=[],s.set(t,a)),a.push(i)}return s}function b(t,e,s,i,a){const n=m(s,a),o=[];for(let s of e){const e=s[i],a=n.get(e)||[];a.length?o.push(...a.map((t=>Object.assign({},g(t),g(s))))):"inner"!==t&&o.push(g(s))}if("outer"===t){const t=m(e,i);for(let e of s){const s=e[a];(t.get(s)||[]).length||o.push(g(e))}}return o}function x(t,e,s,i){return b("left",...arguments)}const v=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function w(t,e=!1){const s=t&&t.match(v);if(s)return s.slice(1);if(e)return null;throw new Error(`Could not understand marker format for ${t}. Should be of format chr:pos or chr:pos_ref/alt`)}class ${constructor(){throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.')}}class z extends ${}class k extends class extends class{constructor(t={}){this._config=t;const{cache_enabled:e=!0,cache_size:s=3}=t;this._enable_cache=e,this._cache=new u(s)}_buildRequestOptions(t,e){return Object.assign({},t)}_getCacheKey(t){if(this._enable_cache)throw new Error("Method not implemented");return null}_performRequest(t){throw new Error("Not implemented")}_normalizeResponse(t,e){return t}_annotateRecords(t,e){return t}_postProcessResponse(t,e){return t}getData(t={},...e){t=this._buildRequestOptions(t,...e);const s=this._getCacheKey(t);let i;return this._enable_cache&&this._cache.has(s)?i=this._cache.get(s):(i=Promise.resolve(this._performRequest(t)).then((e=>this._normalizeResponse(e,t))),this._cache.add(s,i,t._cache_meta),i.catch((t=>this._cache.remove(s)))),i.then((t=>g(t))).then((e=>this._annotateRecords(e,t))).then((e=>this._postProcessResponse(e,t)))}}{constructor(t={}){super(t),this._url=t.url}_getCacheKey(t){return this._getURL(t)}_getURL(t){return this._url}_performRequest(t){const e=this._getURL(t);if(!this._url)throw new Error('Web based resources must specify a resource URL as option "url"');return fetch(e).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}_normalizeResponse(t,e){return"string"==typeof t?JSON.parse(t):t}}{constructor(t={}){t.params&&(console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'),Object.assign(t,t.params||{}),delete t.params),super(t);const{prefix_namespace:e=!0,limit_fields:s}=t;this._prefix_namespace=e,this._limit_fields=!!s&&new Set(s)}_getCacheKey(t){let{chr:e,start:s,end:i}=t;const a=this._cache.find((({metadata:t})=>e===t.chr&&s>=t.start&&i<=t.end));return a&&({chr:e,start:s,end:i}=a.metadata),t._cache_meta={chr:e,start:s,end:i},`${e}_${s}_${i}`}_postProcessResponse(t,e){if(!this._prefix_namespace||!Array.isArray(t))return t;const{_limit_fields:s}=this,{_provider_name:i}=e;return t.map((t=>Object.entries(t).reduce(((t,[e,a])=>(s&&!s.has(e)||(t[`${i}:${e}`]=a),t)),{})))}_findPrefixedKey(t,e){const s=new RegExp(`:${e}$`),i=Object.keys(t).find((t=>s.test(t)));if(!i)throw new Error(`Could not locate the required key name: ${e} in dependent data`);return i}}class E extends k{constructor(t={}){super(t),this._genome_build=t.genome_build||t.build}_validateBuildSource(t,e){if(t&&e||!t&&!e)throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`)}_normalizeResponse(t,e){let s=super._normalizeResponse(...arguments);if(s=s.data||s,Array.isArray(s))return s;const i=Object.keys(s),a=s[i[0]].length;if(!i.every((function(t){return s[t].length===a})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const n=[],o=Object.keys(s);for(let t=0;t25||"GRCh38"===s)return Promise.resolve([]);e=`{${e.join(" ")} }`;const i=this._getURL(t),a=JSON.stringify({query:e});return fetch(i,{method:"POST",body:a,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}_normalizeResponse(t){if("string"!=typeof t)return t;return JSON.parse(t).data}}class O extends E{constructor(t){t.limit_fields||(t.limit_fields=["variant2","position2","correlation"]),super(t)}__find_ld_refvar(t,e){const s=this._findPrefixedKey(e[0],"variant"),i=this._findPrefixedKey(e[0],"log_pvalue");let a,n={};if(t.ldrefvar)a=t.ldrefvar,n=e.find((t=>t[s]===a))||{};else{let t=0;for(let o of e){const{[s]:e,[i]:r}=o;r>t&&(t=r,a=e,n=o)}}n.lz_is_ld_refvar=!0;const o=w(a,!0);if(!o)throw new Error("Could not request LD for a missing or incomplete marker format");const[r,l,h,c]=o;a=`${r}:${l}`,h&&c&&(a+=`_${h}/${c}`);const d=+l;return d&&t.ldrefvar&&t.chr&&(r!==String(t.chr)||dt.end)?(t.ldrefvar=null,this.__find_ld_refvar(t,e)):a}_buildRequestOptions(t,e){if(!e)throw new Error("LD request must depend on association data");const s=super._buildRequestOptions(...arguments);if(!e.length)return s._skip_request=!0,s;s.ld_refvar=this.__find_ld_refvar(t,e);const i=t.genome_build||this._config.build||"GRCh37";let a=t.ld_source||this._config.source||"1000G";const n=t.ld_pop||this._config.population||"ALL";return"1000G"===a&&"GRCh38"===i&&(a="1000G-FRZ09"),this._validateBuildSource(i,null),Object.assign({},s,{genome_build:i,ld_source:a,ld_population:n})}_getURL(t){const e=this._config.method||"rsquare",{chr:s,start:i,end:a,ld_refvar:n,genome_build:o,ld_source:r,ld_population:l}=t;return[super._getURL(t),"genome_builds/",o,"/references/",r,"/populations/",l,"/variants","?correlation=",e,"&variant=",encodeURIComponent(n),"&chrom=",encodeURIComponent(s),"&start=",encodeURIComponent(i),"&stop=",encodeURIComponent(a)].join("")}_getCacheKey(t){const e=super._getCacheKey(t),{ld_refvar:s,ld_source:i,ld_population:a}=t;return`${e}_${s}_${i}_${a}`}_performRequest(t){if(t._skip_request)return Promise.resolve([]);const e=this._getURL(t);let s={data:{}},i=function(t){return fetch(t).then().then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){s.data[e]=(s.data[e]||[]).concat(t.data[e])})),t.next?i(t.next):s}))};return i(e)}}class T extends E{constructor(t){t.limit_fields||(t.limit_fields=["position","recomb_rate"]),super(t)}_getURL(t){const e=t.genome_build||this._config.build;let s=this._config.source;this._validateBuildSource(e,s);const i=e?`&build=${e}`:` and id in ${s}`;return`${super._getURL(t)}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${i}`}}class L extends k{constructor(t={}){super(...arguments);const{data:e}=t;if(!e||Array.isArray(t))throw new Error("'StaticSource' must provide data as required option 'config.data'");this._data=e}_performRequest(t){return Promise.resolve(this._data)}}class j extends E{_getURL(t){const e=(t.genome_build?[t.genome_build]:null)||this._config.build;if(!e||!Array.isArray(e)||!e.length)throw new Error(["Adapter",this.constructor.name,"requires that you specify array of one or more desired genome build names"].join(" "));return[super._getURL(t),"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",e.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}_getCacheKey(t){return this._getURL(t)}}const P=new c;for(let[e,s]of Object.entries(t))P.add(e,s);P.add("StaticJSON",L),P.add("LDLZ2",O);const R=P,I=d3,C={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function D(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function B(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function U(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function q(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function F(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function H(t){return"number"==typeof t}function G(t){return encodeURIComponent(t)}const J=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,s]of Object.entries(e))J.add(t,s);const Z=J;class K{constructor(t){if(!/^(?:\w+:\w+|^\w+)(?:\|\w+)*$/.test(t))throw new Error(`Invalid field specifier: '${t}'`);const[e,...s]=t.split("|");this.full_name=t,this.field_name=e,this.transformations=s.map((t=>Z.get(t)))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[this.field_name]?s=t[this.field_name]:e&&void 0!==e[this.field_name]&&(s=e[this.field_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const V=/^(\*|[\w]+)/,W=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function Y(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=V.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=V.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=W.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function X(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function Q(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const n=t[s.attr];1===e.length?void 0!==n&&a.push([s.attr]):a.push(...Q(n,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...Q(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...Q(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[n,o]of Object.entries(t))a.push(...Q(o,e).map((t=>[n].concat(t)))),"*"===s.attr&&a.push(...Q(o,i).map((t=>[n].concat(t))))}}else if(s.attrs)for(let[e,n]of Object.entries(t)){const[t,o,r]=X(n,s.attrs);r===s.value&&a.push(...Q(n,i).map((t=>[e].concat(t))))}const n=(o=a,r=JSON.stringify,[...new Map(o.map((t=>[r(t),t]))).values()]);var o,r;return n.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),n}function tt(t,e){const s=function(t,e){let s=[];for(let i of Q(t,e))s.push(X(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=Y(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const et=Math.sqrt(3),st={draw(t,e){const s=-Math.sqrt(e/(3*et));t.moveTo(0,2*-s),t.lineTo(-et*s,s),t.lineTo(et*s,s),t.closePath()}};function it(t,e){if(e=e||{},!t||"object"!=typeof t||"object"!=typeof e)throw new Error("Layout and shared namespaces must be provided as objects");for(let[s,i]of Object.entries(t))"namespace"===s?Object.keys(i).forEach((t=>{const s=e[t];s&&(i[t]=s)})):null!==i&&"object"==typeof i&&(t[s]=it(i,e));return t}function at(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=at(t[s],e[s])):t[s]=nt(e[s])}return t}function nt(t){return JSON.parse(JSON.stringify(t))}function ot(t){if(!t)return null;if("triangledown"===t)return st;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return I[e]||null}function rt(t,e,s=null){const i=new Set;if(!s){if(!e.length)return i;const t=e.join("|");s=new RegExp(`(?:{{)?(?:#if *)?((?:${t}):\\w+)`,"g")}for(const a of Object.values(t)){const t=typeof a;let n=[];if("string"===t){let t;for(;null!==(t=s.exec(a));)n.push(t[1])}else{if(null===a||"object"!==t)continue;n=rt(a,e,s)}for(let t of n)i.add(t)}return i}function lt(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>lt(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,n)=>(a[n]=lt(t[n],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const n=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(n,s)}}function ht(t,e,s){return function(t,e,s){return tt(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function ct(t,e){return function(t,e){return tt(t,e).map((t=>t[2]))}(t,e)}class dt{constructor(t,e,s){this._callable=ls.get(t),this._initiator=e,this._params=s||[]}getData(t,...e){const s={plot_state:t,data_layer:this._initiator};return Promise.resolve(this._callable(s,e,...this._params))}}const ut=class{constructor(t){this._sources=t}config_to_sources(t={},e=[],s){const i=new Map,a=Object.keys(t);let n=e.find((t=>"fetch"===t.type));n||(n={type:"fetch",from:a},e.unshift(n));const o=/^\w+$/;for(let[e,s]of Object.entries(t)){if(!o.test(e))throw new Error(`Invalid namespace name: '${e}'. Must contain only alphanumeric characters`);const t=this._sources.get(s);if(!t)throw new Error(`A data layer has requested an item not found in DataSources: data type '${e}' from ${s}`);i.set(e,t),n.from.find((t=>t.split("(")[0]===e))||n.from.push(e)}let r=Array.from(n.from);for(let t of e){let{type:e,name:a,requires:n,params:o}=t;if("fetch"!==e){let l=0;if(a||(a=t.name=`join${l}`,l+=1),i.has(a))throw new Error(`Configuration error: within a layer, join name '${a}' must be unique`);n.forEach((t=>{if(!i.has(t))throw new Error(`Data operation cannot operate on unknown provider '${t}'`)}));const h=new dt(e,s,o);i.set(a,h),r.push(`${a}(${n.join(", ")})`)}}return[i,r]}getData(t,e,s){return s.length?f(t,e,s,!0):Promise.resolve([])}};function _t(){return{showing:!1,selector:null,content_selector:null,hide_delay:null,show:(t,e)=>(this.curtain.showing||(this.curtain.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&>(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function pt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function gt(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class yt{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&>(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class ft{constructor(t){if(!(t instanceof yt))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),n=this.menu.outer_selector.node().getBoundingClientRect(),o=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-n.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-n.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),_=Math.min(o+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${_}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(gt,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class mt extends yt{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class bt extends yt{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(qt(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&>(this.selector,this.layout.style),this}}class xt extends yt{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class vt extends yt{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=I.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+I.select(this).attr("dy").substring(-2).slice(0,-2);I.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class wt extends vt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((n,o)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{n(URL.createObjectURL(t))}))},r.src=t}))}))}}class $t extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),I.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),I.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class zt extends yt{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class kt extends yt{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot._panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class Et extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${qt(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class Mt extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class St extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class Nt extends yt{constructor(t){super(...arguments)}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class At extends yt{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new ft(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class Ot extends yt{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,n={};s.forEach((t=>{const e=a[t];void 0!==e&&(n[t]=nt(e))})),this._selected_item="default",this.button=new ft(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,o=(a,o,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==o[t];i.layout[t]=e?o[t]:n[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return o(r,n,"default"),a.options.forEach(((t,e)=>o(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class Tt extends yt{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new ft(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,n)=>{const o=s.append("tr"),r=`${e}${n}`;o.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",n).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),o.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const Lt=new c;for(let[t,e]of Object.entries(a))Lt.add(t,e);const jt=Lt;class Pt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{this.addWidget(t)})),"panel"===this.type&&I.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}addWidget(t){try{const e=jt.create(t.type,t,this);return this.widgets.push(e),e}catch(t){console.warn("Failed to create widget"),console.error(t)}}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot._panel_boundaries.dragging||this.parent_plot._interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=I.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=I.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const Rt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:14,hidden:!1};class It{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=at(this.parent.layout.legend||{},Rt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((a=>{const n=this.parent.data_layers[a].layout.legend;Array.isArray(n)&&n.forEach((a=>{const n=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),o=+a.label_size||+this.layout.label_size;let r=0,l=o/2+t/2;i=Math.max(i,o+t);const h=a.shape||"",c=ot(h);if("line"===h){const e=+a.length||16,s=o/4+t/2;n.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(gt,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;n.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(gt,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if("ribbon"===h){const e=+a.width||25,s=+a.height||e,i="horizontal"===(a.orientation||"vertical");let o=a.color_stops;const h=n.append("g"),c=h.append("g"),d=h.append("g");let u=0;if(a.tick_labels){let t;t=i?[0,e*o.length-1]:[s*o.length-1,0];const n=I.scaleLinear().domain(I.extent(a.tick_labels)).range(t),r=(i?I.axisTop:I.axisRight)(n).tickSize(3).tickValues(a.tick_labels).tickFormat((t=>t));d.call(r).attr("class","lz-axis"),u=d.node().getBoundingClientRect().height}i?(d.attr("transform",`translate(0, ${u})`),c.attr("transform",`translate(0, ${u})`)):(h.attr("transform","translate(5, 0)"),d.attr("transform",`translate(${e}, 0)`)),i||(o=o.slice(),o.reverse());for(let t=0;tt&&a>this.parent.parent.layout.width&&(s+=i,e=t,n.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(n)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const Ct={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class Dt{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"!=typeof t.id||!t.id)throw new Error('Panel layouts must specify "id"');if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`);this.id=t.id,this._initialized=!1,this._layout_idx=null,this.svg={},this.layout=at(t||{},Ct),this.parent?(this.state=this.parent.state,this._state_id=this.id,this.state[this._state_id]=this.state[this._state_id]||{}):(this.state=null,this._state_id=null),this.data_layers={},this._data_layer_ids_by_z_index=[],this._data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this._zoom_timeout=null,this._event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this._event_hooks[t]&&this._event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=at(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(gt,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id '${t.id}'; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=xe.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this._data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this._data_layer_ids_by_z_index.length+e.layout.z_index,0)),this._data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this._data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id]._layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){const e=this.data_layers[t];if(!e)throw new Error(`Unable to remove data layer, ID not found: ${t}`);return e.destroyAllTooltips(),e.svg.container&&e.svg.container.remove(),this.layout.data_layers.splice(e._layout_idx,1),delete this.state[e._state_id],delete this.data_layers[t],this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id]._layout_idx=e})),this}clearSelections(){return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height);const{cliparea:t}=this.layout,{margin:e}=this.layout;this.inner_border.attr("x",e.left).attr("y",e.top).attr("width",this.parent_plot.layout.width-(e.left+e.right)).attr("height",this.layout.height-(e.top+e.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const s=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),n=Math.pow(10,e);return t===1/0&&(t=n),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,n),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},i={},a=this.layout.axes;if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};a.x.range&&(t.start=a.x.range.start||t.start,t.end=a.x.range.end||t.end),i.x=[t.start,t.end],i.x_shifted=[t.start,t.end]}if(this.y1_extent){const e={start:t.height,end:0};a.y1.range&&(e.start=a.y1.range.start||e.start,e.end=a.y1.range.end||e.end),i.y1=[e.start,e.end],i.y1_shifted=[e.start,e.end]}if(this.y2_extent){const e={start:t.height,end:0};a.y2.range&&(e.start=a.y2.range.start||e.start,e.end=a.y2.range.end||e.end),i.y2=[e.start,e.end],i.y2_shifted=[e.start,e.end]}let{_interaction:n}=this.parent;const o=n.dragging;if(n.panel_id&&(n.panel_id===this.id||n.linked_panel_ids.includes(this.id))){let a,r=null;if(n.zooming&&"function"==typeof this.x_scale){const s=Math.abs(this.x_extent[1]-this.x_extent[0]),o=Math.round(this.x_scale.invert(i.x_shifted[1]))-Math.round(this.x_scale.invert(i.x_shifted[0]));let r=n.zooming.scale;const l=Math.floor(o*(1/r));r<1&&!isNaN(this.parent.layout.max_region_scale)?r=1/(Math.min(l,this.parent.layout.max_region_scale)/o):r>1&&!isNaN(this.parent.layout.min_region_scale)&&(r=1/(Math.max(l,this.parent.layout.min_region_scale)/o));const h=Math.floor(s*r);a=n.zooming.center-e.left-this.layout.origin.x;const c=a/t.width,d=Math.max(Math.floor(this.x_scale.invert(i.x_shifted[0])-(h-o)*c),1);i.x_shifted=[this.x_scale(d),this.x_scale(d+h)]}else if(o)switch(o.method){case"background":i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x;break;case"x_tick":I.event&&I.event.shiftKey?(i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x):(a=o.start_x-e.left-this.layout.origin.x,r=s(a/(a+o.dragged_x),3),i.x_shifted[0]=0,i.x_shifted[1]=Math.max(t.width*(1/r),1));break;case"y1_tick":case"y2_tick":{const n=`y${o.method[1]}_shifted`;I.event&&I.event.shiftKey?(i[n][0]=t.height+o.dragged_y,i[n][1]=+o.dragged_y):(a=t.height-(o.start_y-e.top-this.layout.origin.y),r=s(a/(a-o.dragged_y),3),i[n][0]=t.height,i[n][1]=t.height-t.height*(1/r))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(i[t][0]),this[`${t}_scale`].invert(i[t][1])],this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!I.event.shiftKey&&!I.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(I.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=I.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,I.event.wheelDelta||-I.event.detail||-I.event.deltaY));0!==e&&(this.parent._interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),n=this.parent._interaction,n.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this._zoom_timeout&&clearTimeout(this._zoom_timeout),this._zoom_timeout=setTimeout((()=>{this.parent._interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this._initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{const e=this.layout.axes[t];Object.keys(e).length&&!1!==e.render?(e.render=!0,e.label=e.label||null):e.render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){const s=this.layout;return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),s.height=Math.max(Math.round(+e),s.min_height)),s.cliparea.width=Math.max(this.parent_plot.layout.width-(s.margin.left+s.margin.right),0),s.cliparea.height=Math.max(s.height-(s.margin.top+s.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",s.height),this._initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this._initialized&&this.render(),this}setMargin(t,e,s,i){let a;const{cliparea:n,margin:o}=this.layout;return!isNaN(t)&&t>=0&&(o.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(o.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(o.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(o.left=Math.max(Math.round(+i),0)),o.top+o.bottom>this.layout.height&&(a=Math.floor((o.top+o.bottom-this.layout.height)/2),o.top-=a,o.bottom-=a),o.left+o.right>this.parent_plot.layout.width&&(a=Math.floor((o.left+o.right-this.parent_plot.layout.width)/2),o.left-=a,o.right-=a),["top","right","bottom","left"].forEach((t=>{o[t]=Math.max(o[t],0)})),n.width=Math.max(this.parent_plot.layout.width-(o.left+o.right),0),n.height=Math.max(this.layout.height-(o.top+o.bottom),0),n.origin.x=o.left,n.origin.y=o.top,this._initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=_t.call(this),this.loader=pt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new Pt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new It(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this._data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(I.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent._panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){const{parent:t}=this,e=this.layout.y_index;return t._panel_ids_by_y_index[e-1]&&(t._panel_ids_by_y_index[e]=t._panel_ids_by_y_index[e-1],t._panel_ids_by_y_index[e-1]=this.id,t.applyPanelYIndexesToPanelLayouts(),t.positionPanels()),this}moveDown(){const{_panel_ids_by_y_index:t}=this.parent;return t[this.layout.y_index+1]&&(t[this.layout.y_index]=t[this.layout.y_index+1],t[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this._data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this._data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this._data_promises).then((()=>{this._initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=I.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=I.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this._data_layer_ids_by_z_index.reduce(((e,a)=>{const n=s.data_layers[a];return e.concat(n.getTicks(t,i))}),[]).map((t=>{let s={};return s=at(s,e),at(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,n=1.5,o=.5+1.5*n,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(_=parseFloat(_.toFixed(c)));u.push(_),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eqt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=I.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=I.select(this).select("text");s[`${t}_ticks`][i].style&>(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const n=this.layout.axes[t].label||null;return null!==n&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(Ht(n,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof I.select(this).node().focus&&I.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";I.event&&I.event.shiftKey&&(i="move"),I.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){I.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this._data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this._data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;Dt.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Dt.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Bt={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Ut{constructor(t,e,s){this._initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this._panel_ids_by_y_index=[],this._remap_promises=[],this.layout=s,at(this.layout,Bt),this._base_layout=nt(this.layout),this.state=this.layout.state,this.lzd=new ut(e),this._external_listeners=new Map,this._event_hooks={},this._interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this._event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this._event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Dt(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this._panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this._panel_ids_by_y_index.length+e.layout.y_index,0)),this._panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this._panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id]._layout_idx=s,this._initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t]._data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i._layer_state,delete this.layout.state[i._state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){const e=this.panels[t];if(!e)throw new Error(`Unable to remove panel, ID not found: ${t}`);return this._panel_boundaries.hide(),this.clearPanelData(t),e.loader.hide(),e.toolbar.destroy(!0),e.curtain.hide(),e.svg.container&&e.svg.container.remove(),this.layout.panels.splice(e._layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id]._layout_idx=e})),this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this._initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e){const{from_layer:s,namespace:i,data_operations:a,onerror:n}=t,o=n||function(t){console.error("An error occurred while acting on an external callback",t)};if(s){const t=`${this.getBaseId()}.`,i=s.startsWith(t)?s:`${t}${s}`;let a=!1;for(let t of Object.values(this.panels))if(a=Object.values(t.data_layers).some((t=>t.getBaseId()===i)),a)break;if(!a)throw new Error(`Could not subscribe to unknown data layer ${i}`);const n=t=>{if(t.data.layer===i)try{e(t.data.content,this)}catch(t){o(t)}};return this.on("data_from_layer",n),n}if(!i)throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option");const[r,l]=this.lzd.config_to_sources(i,a),h=()=>{try{this.lzd.getData(this.state,r,l).then((t=>e(t,this))).catch(o)}catch(t){o(t)}};return this.on("data_rendered",h),h}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this._remap_promises=[],this.loading_data=!0;for(let t in this.panels)this._remap_promises.push(this.panels[t].reMap());return Promise.all(this._remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e._data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this._initialized=!1,this.svg=null,this.panels=null}mutateLayout(){Object.values(this.panels).forEach((t=>{Object.values(t.data_layers).forEach((t=>t.mutateLayout()))}))}_canInteract(t){t=t||null;const{_interaction:e}=this;return t?(void 0===e.panel_id||e.panel_id===t)&&!this.loading_data:!(e.dragging||e.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==I.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this._panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");return this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,n=e.layout.height*s;e.setDimensions(a,n),e.setOrigin(0,i),i+=n,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this._initialized&&(this._panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e of Object.values(this.panels))e.layout.interaction.x_linked&&(t.left=Math.max(t.left,e.layout.margin.left),t.right=Math.max(t.right,e.layout.margin.right));let e=0;return this._panel_ids_by_y_index.forEach((s=>{const i=this.panels[s],a=i.layout;if(i.setOrigin(0,e),e+=this.panels[s].layout.height,a.interaction.x_linked){const e=Math.max(t.left-a.margin.left,0)+Math.max(t.right-a.margin.right,0);a.width+=e,a.margin.left=t.left,a.margin.right=t.right,a.cliparea.origin.x=t.left}})),this.setDimensions(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize){I.select(this.container).classed("lz-container-responsive",!0);const t=()=>this.rescaleSVG();if(window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t),"undefined"!=typeof IntersectionObserver){const t={root:document.documentElement,threshold:.9};new IntersectionObserver(((t,e)=>{t.some((t=>t.intersectionRatio>0))&&this.rescaleSVG()}),t).observe(this.container)}const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}if(this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this._mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=_t.call(this),this.loader=pt.call(this),this._panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent._panel_ids_by_y_index.forEach(((t,e)=>{const s=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=I.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent._panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+I.event.dy);const i=t.layout.height-s;this.parent._panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent._panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent._panel_boundaries.selectors.push(s)}));const t=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=I.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+I.event.dx,this.parent._total_height+I.event.dy)})),t.call(e),this.parent._panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent._panel_ids_by_y_index[s]],a=i._getPageOrigin(),n=t.x,o=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${o}px`).style("left",`${n}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&I.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this._panel_boundaries.hide_timeout),this._panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this._panel_boundaries.hide_timeout=setTimeout((()=>{this._panel_boundaries.hide()}),300)})),this.toolbar=new Pt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this._mouse_guide.vertical.attr("x",-1),this._mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=I.mouse(this.svg.node());this._mouse_guide.vertical.attr("x",t[0]),this._mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{const{_interaction:t}=this;if(t.dragging){const e=I.mouse(this.svg.node());I.event&&I.event.preventDefault(),t.dragging.dragged_x=e[0]-t.dragging.start_x,t.dragging.dragged_y=e[1]-t.dragging.start_y,this.panels[t.panel_id].render(),t.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=I.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this._initialized=!0;const a=this.svg.node().getBoundingClientRect(),n=a.width?a.width:this.layout.width,o=a.height?a.height:this._total_height;return this.setDimensions(n,o),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof Dt&&s&&this._canInteract()))return this.stopDrag();const i=I.mouse(this.svg.node());return this._interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){const{_interaction:t}=this;if(!t.dragging)return this;if("object"!=typeof this.panels[t.panel_id])return this._interaction={},this;const e=this.panels[t.panel_id],s=(t,s,i)=>{e._data_layer_ids_by_z_index.forEach((a=>{const n=e.data_layers[a].layout[`${t}_axis`];n.axis===s&&(n.floor=i[0],n.ceiling=i[1],delete n.lower_buffer,delete n.upper_buffer,delete n.min_extent,delete n.ticks)}))};switch(t.dragging.method){case"background":case"x_tick":0!==t.dragging.dragged_x&&(s("x",1,e.x_extent),this.applyState({start:e.x_extent[0],end:e.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==t.dragging.dragged_y){const i=parseInt(t.dragging.method[1]);s("y",i,e[`y${i}_extent`])}}return this._interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function qt(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),n=Math.min(Math.max(e,0),2),o=Math.min(Math.max(a,n),12);let r=`${(t/Math.pow(10,e)).toFixed(o)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Ft(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function Ht(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const n=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(n())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},o=[];for(;i.length>0;)o.push(n());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new K(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return o.map(l).join("")}const Gt=new h;Gt.add("=",((t,e)=>t===e)),Gt.add("!=",((t,e)=>t!=e)),Gt.add("<",((t,e)=>tt<=e)),Gt.add(">",((t,e)=>t>e)),Gt.add(">=",((t,e)=>t>=e)),Gt.add("%",((t,e)=>t%e)),Gt.add("in",((t,e)=>e&&e.includes(t))),Gt.add("match",((t,e)=>t&&t.includes(e)));const Jt=Gt,Zt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Kt=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Wt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Yt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let n=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var n=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(n=i)})),null===n)return a;const t=(+e-s[n-1])/(s[n]-s[n-1]);return isFinite(t)?I.interpolate(i[n-1],i[n])(t):a}};function Qt(t,e){if(void 0===e)return null;const{beta_field:s,stderr_beta_field:i,"+":a=null,"-":n=null}=t;if(!s||!i)throw new Error("effect_direction must specify how to find required 'beta' and 'stderr_beta' fields");const o=e[s],r=e[i];if(void 0!==o)if(void 0!==r){if(o-1.96*r>0)return a;if(o+1.96*r<0)return n||null}else{if(o>0)return a;if(o<0)return n}return null}const te=new h;for(let[t,e]of Object.entries(n))te.add(t,e);te.add("if",Zt);const ee=te,se={id:"",type:"",tag:"custom_data_type",namespace:{},data_operations:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class ie{constructor(t,e){this._initialized=!1,this._layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=at(t||{},se),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=nt(this.layout),this.state={},this._state_id=null,this._layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this._tooltips={}),this._global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1},this._data_contract=new Set,this._entities=new Map,this._dependencies=[],this.mutateLayout()}render(){throw new Error("Method must be implemented")}moveForward(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e+1]&&(t[e]=t[e+1],t[e+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e-1]&&(t[e]=t[e-1],t[e-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this._layer_state.extra_fields[i]||(this._layer_state.extra_fields[i]={}),this._layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}mutateLayout(){if(this.parent_plot){const{namespace:t,data_operations:e}=this.layout;this._data_contract=rt(this.layout,Object.keys(t));const[s,i]=this.parent_plot.lzd.config_to_sources(t,e,this);this._entities=s,this._dependencies=i}}_getDataExtent(t,e){return t=t||this.data,I.extent(t,(t=>+new K(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field;let i=t[s];if(void 0===i&&/{{[^{}]*}}/.test(s)&&(i=Ht(s,t,{})),null==i)throw new Error("Unable to generate element ID");const a=i.toString().replace(/\W/g,""),n=`${this.getBaseId()}-${a}`.replace(/([:.[\],])/g,"_");return t[e]=n,n}getElementStatusNodeId(t){return null}getElementById(t){const e=I.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Jt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new K(t):null;if(this.data.length&&this._data_contract.size){const t=new Set(this._data_contract);for(let e of this.data)if(Object.keys(e).forEach((e=>t.delete(e))),!t.size)break;t.size&&console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...t]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`)}return this.data.forEach(((a,n)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(p+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=_<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-_,0),e=Math.max(c.width/2+_-u,0);y=h.x+_-c.width/2-e+t,b=h.x+_-y-7,"top"===w?(g=h.y+p-(v+c.height+8),f="down",m=c.height-1):(g=h.y+p+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+_+x+8,f="left",b=-8):(y=h.x+_-c.width-x-8,f="right",b=c.width-1),p-c.height/2<=0?(g=h.y+p-10.5-6,m=6):p+c.height/2>=d?(g=h.y+p+7+6-c.height,m=c.height-14-6):(g=h.y+p-c.height/2,m=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${f}`).style("left",`${b}px`).style("top",`${m}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:n}=t,o=Jt.get(i),r=this.getElementAnnotation(e);o(s?new K(s).resolve(e,r):e,n)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this._layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;C.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this._state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this._state_id]=t),this._layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this._tooltips[e])return this._tooltips[e]={data:t,arrow:null,selector:I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this._layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this._tooltips[e].selector.html(""),this._tooltips[e].arrow=null,this.layout.tooltip.html&&this._tooltips[e].selector.html(Ht(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this._tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this._tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this._tooltips[s]&&("object"==typeof this._tooltips[s].selector&&this._tooltips[s].selector.remove(),delete this._tooltips[s]),!e){this._layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this._tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this._tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this._tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this._tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){const s=this.layout.tooltip;if("object"!=typeof s)return this;const i=this.getElementId(t),a=(t,e,s)=>{let i=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",i=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let n;for(let o in e)n=a(t,e[o],o),null===i?i=n:"and"===s?i=i&&n:"or"===s&&(i=i||n)}}return i};let n={};"string"==typeof s.show?n={and:[s.show]}:"object"==typeof s.show&&(n=s.show);let o={};"string"==typeof s.hide?o={and:[s.hide]}:"object"==typeof s.hide&&(o=s.hide);const r=this._layer_state;var l={};C.adjectives.forEach((t=>{const e=`un${t}`;l[t]=r.status_flags[t].has(i),l[e]=!l[t]}));const h=a(l,n),c=a(l,o),d=r.status_flags.has_tooltip.has(i);return!h||!e&&!d||c?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),I.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const n=this.getElementStatusNodeId(e);null!==n&&I.select(`#${n}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const o=!this._layer_state.status_flags[t].has(a);s&&o&&this._layer_state.status_flags[t].add(a),s||o||this._layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,o),o&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!o&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!o&&s||this.parent.emit("match_requested",{value:new K(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this._layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this._layer_state.status_flags[t]=new Set}return this._global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||I.select(I.event.target).datum(),s===!!I.event.ctrlKey&&i===!!I.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a._layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=Ht(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this._layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e._state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this._entities,this._dependencies).then((t=>{this.data=t,this.applyDataMethods(),this._initialized=!0,this.parent.emit("data_from_layer",{layer:this.getBaseId(),content:nt(t)},!0)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;ie.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},ie.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},ie.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},ie.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const ae={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class ne extends ie{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");at(t,ae),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],n=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+n)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const oe={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class re extends ie{constructor(t){if(at(t,oe),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions.length&&t.namespace&&Object.keys(t.namespace).length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>I.ascending(t[s],e[s])||I.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,n){const o=a[a.length-1]||t;if(t[s]===o[s]&&t[i]<=o[e]){const s=Math.min(o[i],t[i]),n=Math.max(o[e],t[e]);t=Object.assign({},o,t,{[i]:s,[e]:n}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const le={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class he extends ie{constructor(t){t=at(t,le),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function n(t){const a=t[e.x_axis.field1],n=t[e.x_axis.field2],o=(a+n)/2,r=[[s(a),i(0)],[s(o),i(t[e.y_axis.field])],[s(n),i(0)]];return I.line().x((t=>t[0])).y((t=>t[1])).curve(I.curveNatural)(r)}const o=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(gt,e.style),o.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(o).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>n(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>n(t))),r.exit().remove(),o.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],n=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:n(t.data[s.y_axis.field]),y_max:n(0)}}}const ce={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:15,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class de extends ie{constructor(t){t=at(t,ce),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=I.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const n=I.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,n.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(n).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),n.exit().remove();const o=I.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));o.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(o).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),o.exit().remove();const r=I.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=I.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=I.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const ue={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5,tooltip:null};class _e extends ie{constructor(t){if((t=at(t,ue)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const n=t.x_scale,o=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?I.area().x((t=>+n(t[e]))).y0(+o(0)).y1((t=>+o(t[s]))):I.line().x((t=>+n(t[e]))).y((t=>+o(t[s]))).curve(I[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(gt,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;void 0===e&&(e=!0),this._global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this._global_statuses).forEach((t=>{this._global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const pe={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class ge extends ie{constructor(t){t=at(t,pe),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments)}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],n=I.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const n=+t[e](s.y);return isNaN(n)?a[i]:n}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",n).call(gt,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=I.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ye={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class fe extends ie{constructor(t){(t=at(t,ye)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),n=Math.sqrt(a/Math.PI);return{x_min:e-n,x_max:e+n,y_min:i-n,y_max:i+n}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,n=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,o=(t,a)=>{const n=+t.attr("x"),o=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",n-o),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",n+o),i&&a.attr("x2",r+l))};t._label_texts.each((function(e,a){const r=I.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>n){const e=i?I.select(t._label_lines.nodes()[a]):null;o(r,e)}})),t._label_texts.each((function(e,n){const r=I.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?I.select(t._label_lines.nodes()[n]):null;t._label_texts.each((function(){const t=I.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(o(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=o.attr("y"),c=.5*(r.topp?(g=d-+n,d=+n,u-=g):u+l.height/2>p&&(g=u-+h,u=+h,d-=g),a.attr("y",d),o.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t._label_texts.nodes();t._label_lines.attr("y2",((t,s)=>I.select(e[s]).attr("y")))}this._label_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let n=this._applyFilters();if(n.forEach((t=>{let n=e(t[this.layout.x_axis.field]),o=s(t[this.layout.y_axis.field]);isNaN(n)&&(n=-1e3),isNaN(o)&&(o=-1e3),t[i]=n,t[a]=o})),this.layout.coalesce.active&&n.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:o,x_gap:r,y_gap:l}=this.layout.coalesce;n=function(t,e,s,i,a,n,o){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function _(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function p(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],f=g>=e&&g<=s&&y>=a&&y<=n;t.lz_is_match||!f?(_(),r.push(t)):null===c?p(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=o?u.push(t):(_(),p(g,y,t))})),_(),r}(n,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(o)?s(+o):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=n.filter(t)}else e=n;this._label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const o=`lz-data_layer-${this.layout.type}-label`,r=this._label_groups.enter().append("g").attr("class",o);this._label_texts&&this._label_texts.remove(),this._label_texts=this._label_groups.merge(r).append("text").text((e=>Ht(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(gt,t.layout.label.style||{}),t.layout.label.lines&&(this._label_lines&&this._label_lines.remove(),this._label_lines=this._label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(gt,t.layout.label.lines.style||{})),this._label_groups.exit().remove()}else this._label_texts&&this._label_texts.remove(),this._label_lines&&this._label_lines.remove(),this._label_groups&&this._label_groups.remove();const o=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(n,(t=>t[this.layout.id_field])),r=I.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>ot(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;o.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(o).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),o.exit().remove(),this.layout.label&&(this.flip_labels(),this._label_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=I.select(I.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class me extends fe{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],n="string"==typeof i?i.toLowerCase():i,o="string"==typeof a?a.toLowerCase():a;return n===o?0:n{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],n=i[e],o=s[a]||[n,n];s[a]=[Math.min(o[0],n),Math.max(o[1],n)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const o=i[t];let r;switch(s){case"left":r=o[0];break;case"center":const t=o[1]-o[0];r=o[0]+(0!==t?t:o[0])/2;break;case"right":r=o[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const be=new c;for(let[t,e]of Object.entries(o))be.add(t,e);const xe=be,ve=7.301,we={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{assoc:variant|htmlescape}}
          \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
          \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
          \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
          '},$e=function(){const t=nt(we);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),ze={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

          {{gene_name|htmlescape}}

          Gene ID: {{gene_id|htmlescape}}
          Transcript ID: {{transcript_id|htmlescape}}
          {{#if pLI}}
          ConstraintExpected variantsObserved variantsConst. Metric
          Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
          o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
          Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
          o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
          pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
          o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

          {{/if}}More data on gnomAD'},ke={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{catalog:variant|htmlescape}}
          Catalog entries: {{n_catalog_matches|htmlescape}}
          Top Trait: {{catalog:trait|htmlescape}}
          Top P Value: {{catalog:log_pvalue|logtoscinotation}}
          More: GWAS catalog / dbSNP'},Ee={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
          {{access:start1|htmlescape}}-{{access:end1|htmlescape}}
          Promoter
          {{access:start2|htmlescape}}-{{access:end2|htmlescape}}
          {{#if access:target}}Target: {{access:target|htmlescape}}
          {{/if}}Score: {{access:score|htmlescape}}"},Me={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:ve},Se={id:"recombrate",namespace:{recomb:"recomb"},data_operations:[{type:"fetch",from:["recomb"]}],type:"line",tag:"recombination",z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"recomb:position"},y_axis:{axis:2,field:"recomb:recomb_rate",floor:0,ceiling:100}},Ne={namespace:{assoc:"assoc",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)"]},{type:"left_match",name:"assoc_plus_ld",requires:["assoc","ld"],params:["assoc:position","ld:position2"]}],id:"associationpvalues",type:"scatter",tag:"association",id_field:"assoc:variant",coalesce:{active:!0},point_shape:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"diamond"}},{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"assoc:beta",stderr_beta_field:"assoc:se"}},"circle"],point_size:{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:80,else:40}},color:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"#9632b8"}},{scale_function:"numerical_bin",field:"ld:correlation",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{label:"LD (r²)",label_size:14},{shape:"ribbon",orientation:"vertical",width:10,height:15,color_stops:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"],tick_labels:[0,.2,.4,.6,.8,1]}],label:null,z_index:2,x_axis:{field:"assoc:position"},y_axis:{axis:1,field:"assoc:log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(we)},Ae={id:"coaccessibility",type:"arcs",tag:"coaccessibility",namespace:{access:"access"},data_operations:[{type:"fetch",from:["access"]}],match:{send:"access:target",receive:"access:target"},id_field:"{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}",filters:[{field:"access:score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"access:start1",field2:"access:start2"},y_axis:{axis:1,field:"access:score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(Ee)},Oe=function(){let t=nt(Ne);return t=at({id:"associationpvaluescatalog",fill_opacity:.7},t),t.data_operations.push({type:"assoc_to_gwas_catalog",name:"assoc_catalog",requires:["assoc_plus_ld","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}),t.tooltip.html+='{{#if catalog:rsid}}
          See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t}(),Te={id:"phewaspvalues",type:"category_scatter",tag:"phewas",namespace:{phewas:"phewas"},data_operations:[{type:"fetch",from:["phewas"]}],point_shape:[{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"phewas:beta",stderr_beta_field:"phewas:se"}},"circle"],point_size:70,tooltip_positioning:"vertical",id_field:"{{phewas:trait_group}}_{{phewas:trait_label}}",x_axis:{field:"lz_auto_x",category_field:"phewas:trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"phewas:log_pvalue",floor:0,upper_buffer:.15},color:[{field:"phewas:trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Trait: {{phewas:trait_label|htmlescape}}
          \nTrait Category: {{phewas:trait_group|htmlescape}}
          \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
          β: {{phewas:beta|scinotation|htmlescape}}
          {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}"},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{phewas:trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"phewas:log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},Le={namespace:{gene:"gene",constraint:"constraint"},data_operations:[{type:"fetch",from:["gene","constraint(gene)"]},{name:"gene_constraint",type:"genes_to_gnomad_constraint",requires:["gene","constraint"]}],id:"genes",type:"genes",tag:"genes",id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ze)},je=at({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},nt(Le)),Pe={namespace:{assoc:"assoc",catalog:"catalog"},data_operations:[{type:"fetch",from:["assoc","catalog"]},{type:"assoc_to_gwas_catalog",name:"assoc_plus_ld",requires:["assoc","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}],id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:"#0000CC",filters:[{field:"catalog:rsid",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ke),tooltip_positioning:"top"},Re={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},Ie={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},Ce={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},De={widgets:[{type:"title",title:"LocusZoom",subtitle:`v${l}`,position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Be=function(){const t=nt(De);return t.widgets.push(nt(Re)),t}(),Ue=function(){const t=nt(De);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),qe={id:"association",tag:"association",min_height:200,height:300,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:50},y2:{label:"Recombination Rate (cM/Mb)",label_offset:46}},legend:{orientation:"vertical",origin:{x:75,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Me),nt(Se),nt(Ne)]},Fe={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:40,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Ae)]},He=function(){let t=nt(qe);return t=at({id:"associationcatalog"},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{catalog:trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"catalog:trait",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve},{field:"ld:correlation",operator:">",value:.4}],style:{"font-size":"12px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[nt(Me),nt(Se),nt(Oe)],t}(),Ge={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:55,bottom:20,left:70},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},nt(Ie)),t}(),data_layers:[nt(je)]},Je={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:55,bottom:120,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:50}},data_layers:[nt(Me),nt(Te)]},Ze={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:55,bottom:10,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Pe)]},Ke={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[nt(qe),nt(Ge)]},Ve={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[Ze,He,Ge]},We={width:800,responsive_resize:!0,toolbar:De,panels:[nt(Je),at({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"}}},nt(Ge))],mouse_guide:!1},Ye={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:nt(De),panels:[nt(Fe),function(){const t=Object.assign({height:270},nt(Ge)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},Xe={standard_association:we,standard_association_with_label:$e,standard_genes:ze,catalog_variant:ke,coaccessibility:Ee},Qe={ldlz2_pop_selector:Re,gene_selector_menu:Ie},ts={standard_panel:Ce,standard_plot:De,standard_association:Be,region_nav_plot:Ue},es={significance:Me,recomb_rate:Se,association_pvalues:Ne,coaccessibility:Ae,association_pvalues_catalog:Oe,phewas_pvalues:Te,genes:Le,genes_filtered:je,annotation_catalog:Pe},ss={association:qe,coaccessibility:Fe,association_catalog:He,genes:Ge,phewas:Je,annotation_catalog:Ze},is={standard_association:Ke,association_catalog:Ve,standard_phewas:We,coaccessibility:Ye};const as=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);const a=s.namespace;i.namespace||delete s.namespace;let n=at(s,i);return a&&(n=it(n,a)),nt(n)}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=nt(s);return"data_layer"===t&&a.namespace&&(a._auto_fields=[...rt(a,Object.keys(a.namespace))].sort()),super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return at(t,e)}renameField(){return lt(...arguments)}mutate_attrs(){return ht(...arguments)}query_attrs(){return ct(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))as.add(t,s,i);const ns=as,os=new h;function rs(t){return(e,s,...i)=>{if(2!==s.length)throw new Error("Join functions must receive exactly two recordsets");return t(...s,...i)}}os.add("left_match",rs(x)),os.add("inner_match",rs((function(t,e,s,i){return b("inner",...arguments)}))),os.add("full_outer_match",rs((function(t,e,s,i){return b("outer",...arguments)}))),os.add("assoc_to_gwas_catalog",rs((function(t,e,s,i,a){if(!t.length)return t;const n=m(e,i),o=[];for(let t of n.values()){let e,s=0;for(let i of t){const t=i[a];t>=s&&(e=i,s=t)}e.n_catalog_matches=t.length,o.push(e)}return x(t,o,s,i)}))),os.add("genes_to_gnomad_constraint",rs((function(t,e){return t.forEach((function(t){const s=`_${t.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=e[s]&&e[s].gnomad_constraint;i&&Object.keys(i).forEach((function(e){let s=i[e];void 0===t[e]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),t[e]=s)}))})),t})));const ls=os;const hs={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return I.select(t).html(""),I.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!I.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Ut(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Ft(s[2]),e=Ft(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Ft(s[2]),end:Ft(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Ft(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=I.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(gt,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||R}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:R,DataLayers:xe,DataFunctions:ls,Layouts:ns,MatchFunctions:Jt,ScaleFunctions:ee,TransformationFunctions:Z,Widgets:jt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),R}},cs=[];hs.use=function(t,...e){if(!cs.includes(t)){if(e.unshift(hs),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}cs.push(t)}};const ds=hs})(),LocusZoom=i.default})(); +/*! Locuszoom 0.14.0-beta.4 */ +var LocusZoom;(()=>{var t={483:(t,e,s)=>{"use strict";const i=s(919);t.exports=function(t,...e){if(!t){if(1===e.length&&e[0]instanceof Error)throw e[0];throw new i(e)}}},919:(t,e,s)=>{"use strict";const i=s(820);t.exports=class extends Error{constructor(t){super(t.filter((t=>""!==t)).map((t=>"string"==typeof t?t:t instanceof Error?t.message:i(t))).join(" ")||"Unknown error"),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e.assert)}}},820:t=>{"use strict";t.exports=function(...t){try{return JSON.stringify.apply(null,t)}catch(t){return"[Cannot display object: "+t.message+"]"}}},681:(t,e,s)=>{"use strict";const i=s(483),a={};e.B=class{constructor(){this._items=[],this.nodes=[]}add(t,e){const s=[].concat((e=e||{}).before||[]),a=[].concat(e.after||[]),n=e.group||"?",o=e.sort||0;i(!s.includes(n),`Item cannot come before itself: ${n}`),i(!s.includes("?"),"Item cannot come before unassociated items"),i(!a.includes(n),`Item cannot come after itself: ${n}`),i(!a.includes("?"),"Item cannot come after unassociated items"),Array.isArray(t)||(t=[t]);for(const e of t){const t={seq:this._items.length,sort:o,before:s,after:a,group:n,node:e};this._items.push(t)}if(!e.manual){const t=this._sort();i(t,"item","?"!==n?`added into group ${n}`:"","created a dependencies error")}return this.nodes}merge(t){Array.isArray(t)||(t=[t]);for(const e of t)if(e)for(const t of e._items)this._items.push(Object.assign({},t));this._items.sort(a.mergeSort);for(let t=0;tt.sort===e.sort?0:t.sort{function e(t){if("string"==typeof t.source.flags)return t.source.flags;var e=[];return t.global&&e.push("g"),t.ignoreCase&&e.push("i"),t.multiline&&e.push("m"),t.sticky&&e.push("y"),t.unicode&&e.push("u"),e.join("")}t.exports=function t(s){if("function"==typeof s)return s;var i=Array.isArray(s)?[]:{};for(var a in s){var n=s[a],o={}.toString.call(n).slice(8,-1);i[a]="Array"==o||"Object"==o?t(n):"Date"==o?new Date(n.getTime()):"RegExp"==o?RegExp(n.source,e(n)):n}return i}}},e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={exports:{}};return t[i](a,a.exports,s),a.exports}s.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return s.d(e,{a:e}),e},s.d=(t,e)=>{for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),s.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";s.d(i,{default:()=>ds});var t={};s.r(t),s.d(t,{AssociationLZ:()=>M,BaseAdapter:()=>$,BaseApiAdapter:()=>z,BaseLZAdapter:()=>k,BaseUMAdapter:()=>E,GeneConstraintLZ:()=>A,GeneLZ:()=>N,GwasCatalogLZ:()=>S,LDServer:()=>O,PheWASLZ:()=>j,RecombLZ:()=>T,StaticSource:()=>L});var e={};s.r(e),s.d(e,{htmlescape:()=>F,is_numeric:()=>H,log10:()=>D,logtoscinotation:()=>U,neglog10:()=>B,scinotation:()=>q,urlencode:()=>G});var a={};s.r(a),s.d(a,{BaseWidget:()=>yt,_Button:()=>ft,display_options:()=>Ot,download:()=>vt,download_png:()=>wt,filter_field:()=>xt,menu:()=>St,move_panel_down:()=>kt,move_panel_up:()=>zt,region_scale:()=>bt,remove_panel:()=>$t,resize_to_data:()=>Nt,set_state:()=>Tt,shift_region:()=>Et,title:()=>mt,toggle_legend:()=>At,zoom_region:()=>Mt});var n={};s.r(n),s.d(n,{categorical_bin:()=>Vt,effect_direction:()=>Qt,if_value:()=>Zt,interpolate:()=>Xt,numerical_bin:()=>Kt,ordinal_cycle:()=>Wt,stable_choice:()=>Yt});var o={};s.r(o),s.d(o,{BaseDataLayer:()=>ie,annotation_track:()=>ne,arcs:()=>he,category_scatter:()=>me,genes:()=>de,highlight_regions:()=>re,line:()=>_e,orthogonal_line:()=>ge,scatter:()=>fe});var r={};s.r(r),s.d(r,{data_layer:()=>es,panel:()=>ss,plot:()=>is,toolbar:()=>ts,toolbar_widgets:()=>Qe,tooltip:()=>Xe});const l="0.14.0-beta.4";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}class d{constructor(t,e,s={},i=null,a=null){this.key=t,this.value=e,this.metadata=s,this.prev=i,this.next=a}}class u{constructor(t=3){if(this._max_size=t,this._cur_size=0,this._store=new Map,this._head=null,this._tail=null,null===t||t<0)throw new Error('Cache "max_size" must be >= 0')}has(t){return this._store.has(t)}get(t){const e=this._store.get(t);return e?(this._head!==e&&this.add(t,e.value),e.value):null}add(t,e,s={}){if(0===this._max_size)return;const i=this._store.get(t);i&&this._remove(i);const a=new d(t,e,s,null,this._head);if(this._head?this._head.prev=a:this._tail=a,this._head=a,this._store.set(t,a),this._max_size>=0&&this._cur_size>=this._max_size){const t=this._tail;this._tail=this._tail.prev,this._remove(t)}this._cur_size+=1}clear(){this._head=null,this._tail=null,this._cur_size=0,this._store=new Map}remove(t){const e=this._store.get(t);return!!e&&(this._remove(e),!0)}_remove(t){null!==t.prev?t.prev.next=t.next:this._head=t.next,null!==t.next?t.next.prev=t.prev:this._tail=t.prev,this._store.delete(t.key),this._cur_size-=1}find(t){let e=this._head;for(;e;){const s=e.next;if(t(e))return e;e=s}}}var _=s(957),p=s.n(_);function g(t){return"object"!=typeof t?t:p()(t)}var y=s(681);function f(t,e,s,i=!0){if(!s.length)return[];const a=s.map((t=>function(t){const e=/^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(t);if(!e)throw new Error(`Unable to parse dependency specification: ${t}`);let{name_alone:s,name_deps:i,deps:a}=e.groups;return s?[s,[]]:(a=a.split(/\s*,\s*/),[i,a])}(t))),n=new Map(a),o=new y.B;for(let[t,e]of n.entries())try{o.add(t,{after:e,group:t})}catch(e){throw new Error(`Invalid or possible circular dependency specification for: ${t}`)}const r=o.nodes,l=new Map;for(let s of r){const i=e.get(s);if(!i)throw new Error(`Data has been requested from source '${s}', but no matching source was provided`);const a=n.get(s)||[],o=Promise.all(a.map((t=>l.get(t)))).then((e=>{const a=Object.assign({_provider_name:s},t);return i.getData(a,...e)}));l.set(s,o)}return Promise.all([...l.values()]).then((t=>i?t[t.length-1]:t))}function m(t,e){const s=new Map;for(let i of t){const t=i[e];if(void 0===t)throw new Error(`All records must specify a value for the field "${e}"`);if("object"==typeof t)throw new Error("Attempted to group on a field with non-primitive values");let a=s.get(t);a||(a=[],s.set(t,a)),a.push(i)}return s}function b(t,e,s,i,a){const n=m(s,a),o=[];for(let s of e){const e=s[i],a=n.get(e)||[];a.length?o.push(...a.map((t=>Object.assign({},g(t),g(s))))):"inner"!==t&&o.push(g(s))}if("outer"===t){const t=m(e,i);for(let e of s){const s=e[a];(t.get(s)||[]).length||o.push(g(e))}}return o}function x(t,e,s,i){return b("left",...arguments)}const v=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function w(t,e=!1){const s=t&&t.match(v);if(s)return s.slice(1);if(e)return null;throw new Error(`Could not understand marker format for ${t}. Should be of format chr:pos or chr:pos_ref/alt`)}class ${constructor(){throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.')}}class z extends ${}class k extends class extends class{constructor(t={}){this._config=t;const{cache_enabled:e=!0,cache_size:s=3}=t;this._enable_cache=e,this._cache=new u(s)}_buildRequestOptions(t,e){return Object.assign({},t)}_getCacheKey(t){if(this._enable_cache)throw new Error("Method not implemented");return null}_performRequest(t){throw new Error("Not implemented")}_normalizeResponse(t,e){return t}_annotateRecords(t,e){return t}_postProcessResponse(t,e){return t}getData(t={},...e){t=this._buildRequestOptions(t,...e);const s=this._getCacheKey(t);let i;return this._enable_cache&&this._cache.has(s)?i=this._cache.get(s):(i=Promise.resolve(this._performRequest(t)).then((e=>this._normalizeResponse(e,t))),this._cache.add(s,i,t._cache_meta),i.catch((t=>this._cache.remove(s)))),i.then((t=>g(t))).then((e=>this._annotateRecords(e,t))).then((e=>this._postProcessResponse(e,t)))}}{constructor(t={}){super(t),this._url=t.url}_getCacheKey(t){return this._getURL(t)}_getURL(t){return this._url}_performRequest(t){const e=this._getURL(t);if(!this._url)throw new Error('Web based resources must specify a resource URL as option "url"');return fetch(e).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}_normalizeResponse(t,e){return"string"==typeof t?JSON.parse(t):t}}{constructor(t={}){t.params&&(console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'),Object.assign(t,t.params||{}),delete t.params),super(t);const{prefix_namespace:e=!0,limit_fields:s}=t;this._prefix_namespace=e,this._limit_fields=!!s&&new Set(s)}_getCacheKey(t){let{chr:e,start:s,end:i}=t;const a=this._cache.find((({metadata:t})=>e===t.chr&&s>=t.start&&i<=t.end));return a&&({chr:e,start:s,end:i}=a.metadata),t._cache_meta={chr:e,start:s,end:i},`${e}_${s}_${i}`}_postProcessResponse(t,e){if(!this._prefix_namespace||!Array.isArray(t))return t;const{_limit_fields:s}=this,{_provider_name:i}=e;return t.map((t=>Object.entries(t).reduce(((t,[e,a])=>(s&&!s.has(e)||(t[`${i}:${e}`]=a),t)),{})))}_findPrefixedKey(t,e){const s=new RegExp(`:${e}$`),i=Object.keys(t).find((t=>s.test(t)));if(!i)throw new Error(`Could not locate the required key name: ${e} in dependent data`);return i}}class E extends k{constructor(t={}){super(t),this._genome_build=t.genome_build||t.build}_validateBuildSource(t,e){if(t&&e||!t&&!e)throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`)}_normalizeResponse(t,e){let s=super._normalizeResponse(...arguments);if(s=s.data||s,Array.isArray(s))return s;const i=Object.keys(s),a=s[i[0]].length;if(!i.every((function(t){return s[t].length===a})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const n=[],o=Object.keys(s);for(let t=0;t25||"GRCh38"===s)return Promise.resolve([]);e=`{${e.join(" ")} }`;const i=this._getURL(t),a=JSON.stringify({query:e});return fetch(i,{method:"POST",body:a,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}_normalizeResponse(t){if("string"!=typeof t)return t;return JSON.parse(t).data}}class O extends E{constructor(t){t.limit_fields||(t.limit_fields=["variant2","position2","correlation"]),super(t)}__find_ld_refvar(t,e){const s=this._findPrefixedKey(e[0],"variant"),i=this._findPrefixedKey(e[0],"log_pvalue");let a,n={};if(t.ldrefvar)a=t.ldrefvar,n=e.find((t=>t[s]===a))||{};else{let t=0;for(let o of e){const{[s]:e,[i]:r}=o;r>t&&(t=r,a=e,n=o)}}n.lz_is_ld_refvar=!0;const o=w(a,!0);if(!o)throw new Error("Could not request LD for a missing or incomplete marker format");const[r,l,h,c]=o;a=`${r}:${l}`,h&&c&&(a+=`_${h}/${c}`);const d=+l;return d&&t.ldrefvar&&t.chr&&(r!==String(t.chr)||dt.end)?(t.ldrefvar=null,this.__find_ld_refvar(t,e)):a}_buildRequestOptions(t,e){if(!e)throw new Error("LD request must depend on association data");const s=super._buildRequestOptions(...arguments);if(!e.length)return s._skip_request=!0,s;s.ld_refvar=this.__find_ld_refvar(t,e);const i=t.genome_build||this._config.build||"GRCh37";let a=t.ld_source||this._config.source||"1000G";const n=t.ld_pop||this._config.population||"ALL";return"1000G"===a&&"GRCh38"===i&&(a="1000G-FRZ09"),this._validateBuildSource(i,null),Object.assign({},s,{genome_build:i,ld_source:a,ld_population:n})}_getURL(t){const e=this._config.method||"rsquare",{chr:s,start:i,end:a,ld_refvar:n,genome_build:o,ld_source:r,ld_population:l}=t;return[super._getURL(t),"genome_builds/",o,"/references/",r,"/populations/",l,"/variants","?correlation=",e,"&variant=",encodeURIComponent(n),"&chrom=",encodeURIComponent(s),"&start=",encodeURIComponent(i),"&stop=",encodeURIComponent(a)].join("")}_getCacheKey(t){const e=super._getCacheKey(t),{ld_refvar:s,ld_source:i,ld_population:a}=t;return`${e}_${s}_${i}_${a}`}_performRequest(t){if(t._skip_request)return Promise.resolve([]);const e=this._getURL(t);let s={data:{}},i=function(t){return fetch(t).then().then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){s.data[e]=(s.data[e]||[]).concat(t.data[e])})),t.next?i(t.next):s}))};return i(e)}}class T extends E{constructor(t){t.limit_fields||(t.limit_fields=["position","recomb_rate"]),super(t)}_getURL(t){const e=t.genome_build||this._config.build;let s=this._config.source;this._validateBuildSource(e,s);const i=e?`&build=${e}`:` and id in ${s}`;return`${super._getURL(t)}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${i}`}}class L extends k{constructor(t={}){super(...arguments);const{data:e}=t;if(!e||Array.isArray(t))throw new Error("'StaticSource' must provide data as required option 'config.data'");this._data=e}_performRequest(t){return Promise.resolve(this._data)}}class j extends E{_getURL(t){const e=(t.genome_build?[t.genome_build]:null)||this._config.build;if(!e||!Array.isArray(e)||!e.length)throw new Error(["Adapter",this.constructor.name,"requires that you specify array of one or more desired genome build names"].join(" "));return[super._getURL(t),"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",e.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}_getCacheKey(t){return this._getURL(t)}}const P=new c;for(let[e,s]of Object.entries(t))P.add(e,s);P.add("StaticJSON",L),P.add("LDLZ2",O);const R=P,I=d3,C={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function D(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function B(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function U(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function q(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function F(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function H(t){return"number"==typeof t}function G(t){return encodeURIComponent(t)}const J=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,s]of Object.entries(e))J.add(t,s);const Z=J;class K{constructor(t){if(!/^(?:\w+:\w+|^\w+)(?:\|\w+)*$/.test(t))throw new Error(`Invalid field specifier: '${t}'`);const[e,...s]=t.split("|");this.full_name=t,this.field_name=e,this.transformations=s.map((t=>Z.get(t)))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[this.field_name]?s=t[this.field_name]:e&&void 0!==e[this.field_name]&&(s=e[this.field_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const V=/^(\*|[\w]+)/,W=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function Y(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=V.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=V.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=W.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function X(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function Q(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const n=t[s.attr];1===e.length?void 0!==n&&a.push([s.attr]):a.push(...Q(n,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...Q(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...Q(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[n,o]of Object.entries(t))a.push(...Q(o,e).map((t=>[n].concat(t)))),"*"===s.attr&&a.push(...Q(o,i).map((t=>[n].concat(t))))}}else if(s.attrs)for(let[e,n]of Object.entries(t)){const[t,o,r]=X(n,s.attrs);r===s.value&&a.push(...Q(n,i).map((t=>[e].concat(t))))}const n=(o=a,r=JSON.stringify,[...new Map(o.map((t=>[r(t),t]))).values()]);var o,r;return n.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),n}function tt(t,e){const s=function(t,e){let s=[];for(let i of Q(t,e))s.push(X(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=Y(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const et=Math.sqrt(3),st={draw(t,e){const s=-Math.sqrt(e/(3*et));t.moveTo(0,2*-s),t.lineTo(-et*s,s),t.lineTo(et*s,s),t.closePath()}};function it(t,e){if(e=e||{},!t||"object"!=typeof t||"object"!=typeof e)throw new Error("Layout and shared namespaces must be provided as objects");for(let[s,i]of Object.entries(t))"namespace"===s?Object.keys(i).forEach((t=>{const s=e[t];s&&(i[t]=s)})):null!==i&&"object"==typeof i&&(t[s]=it(i,e));return t}function at(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=at(t[s],e[s])):t[s]=nt(e[s])}return t}function nt(t){return JSON.parse(JSON.stringify(t))}function ot(t){if(!t)return null;if("triangledown"===t)return st;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return I[e]||null}function rt(t,e,s=null){const i=new Set;if(!s){if(!e.length)return i;const t=e.join("|");s=new RegExp(`(?:{{)?(?:#if *)?((?:${t}):\\w+)`,"g")}for(const a of Object.values(t)){const t=typeof a;let n=[];if("string"===t){let t;for(;null!==(t=s.exec(a));)n.push(t[1])}else{if(null===a||"object"!==t)continue;n=rt(a,e,s)}for(let t of n)i.add(t)}return i}function lt(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>lt(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,n)=>(a[n]=lt(t[n],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const n=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(n,s)}}function ht(t,e,s){return function(t,e,s){return tt(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function ct(t,e){return function(t,e){return tt(t,e).map((t=>t[2]))}(t,e)}class dt{constructor(t,e,s){this._callable=ls.get(t),this._initiator=e,this._params=s||[]}getData(t,...e){const s={plot_state:t,data_layer:this._initiator};return Promise.resolve(this._callable(s,e,...this._params))}}const ut=class{constructor(t){this._sources=t}config_to_sources(t={},e=[],s){const i=new Map,a=Object.keys(t);let n=e.find((t=>"fetch"===t.type));n||(n={type:"fetch",from:a},e.unshift(n));const o=/^\w+$/;for(let[e,s]of Object.entries(t)){if(!o.test(e))throw new Error(`Invalid namespace name: '${e}'. Must contain only alphanumeric characters`);const t=this._sources.get(s);if(!t)throw new Error(`A data layer has requested an item not found in DataSources: data type '${e}' from ${s}`);i.set(e,t),n.from.find((t=>t.split("(")[0]===e))||n.from.push(e)}let r=Array.from(n.from);for(let t of e){let{type:e,name:a,requires:n,params:o}=t;if("fetch"!==e){let l=0;if(a||(a=t.name=`join${l}`,l+=1),i.has(a))throw new Error(`Configuration error: within a layer, join name '${a}' must be unique`);n.forEach((t=>{if(!i.has(t))throw new Error(`Data operation cannot operate on unknown provider '${t}'`)}));const h=new dt(e,s,o);i.set(a,h),r.push(`${a}(${n.join(", ")})`)}}return[i,r]}getData(t,e,s){return s.length?f(t,e,s,!0):Promise.resolve([])}};function _t(){return{showing:!1,selector:null,content_selector:null,hide_delay:null,show:(t,e)=>(this.curtain.showing||(this.curtain.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&>(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function pt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function gt(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class yt{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&>(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class ft{constructor(t){if(!(t instanceof yt))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),n=this.menu.outer_selector.node().getBoundingClientRect(),o=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-n.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-n.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),_=Math.min(o+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${_}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(gt,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class mt extends yt{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class bt extends yt{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(qt(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&>(this.selector,this.layout.style),this}}class xt extends yt{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class vt extends yt{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=I.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+I.select(this).attr("dy").substring(-2).slice(0,-2);I.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class wt extends vt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((n,o)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{n(URL.createObjectURL(t))}))},r.src=t}))}))}}class $t extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),I.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),I.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class zt extends yt{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class kt extends yt{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot._panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class Et extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${qt(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class Mt extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class St extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class Nt extends yt{constructor(t){super(...arguments)}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class At extends yt{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new ft(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class Ot extends yt{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,n={};s.forEach((t=>{const e=a[t];void 0!==e&&(n[t]=nt(e))})),this._selected_item="default",this.button=new ft(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,o=(a,o,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==o[t];i.layout[t]=e?o[t]:n[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return o(r,n,"default"),a.options.forEach(((t,e)=>o(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class Tt extends yt{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new ft(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,n)=>{const o=s.append("tr"),r=`${e}${n}`;o.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",n).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),o.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const Lt=new c;for(let[t,e]of Object.entries(a))Lt.add(t,e);const jt=Lt;class Pt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{this.addWidget(t)})),"panel"===this.type&&I.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}addWidget(t){try{const e=jt.create(t.type,t,this);return this.widgets.push(e),e}catch(t){console.warn("Failed to create widget"),console.error(t)}}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot._panel_boundaries.dragging||this.parent_plot._interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=I.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=I.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const Rt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:14,hidden:!1};class It{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=at(this.parent.layout.legend||{},Rt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((a=>{const n=this.parent.data_layers[a].layout.legend;Array.isArray(n)&&n.forEach((a=>{const n=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),o=+a.label_size||+this.layout.label_size;let r=0,l=o/2+t/2;i=Math.max(i,o+t);const h=a.shape||"",c=ot(h);if("line"===h){const e=+a.length||16,s=o/4+t/2;n.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(gt,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;n.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(gt,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if("ribbon"===h){const e=+a.width||25,s=+a.height||e,i="horizontal"===(a.orientation||"vertical");let o=a.color_stops;const h=n.append("g"),c=h.append("g"),d=h.append("g");let u=0;if(a.tick_labels){let t;t=i?[0,e*o.length-1]:[s*o.length-1,0];const n=I.scaleLinear().domain(I.extent(a.tick_labels)).range(t),r=(i?I.axisTop:I.axisRight)(n).tickSize(3).tickValues(a.tick_labels).tickFormat((t=>t));d.call(r).attr("class","lz-axis"),u=d.node().getBoundingClientRect().height}i?(d.attr("transform",`translate(0, ${u})`),c.attr("transform",`translate(0, ${u})`)):(h.attr("transform","translate(5, 0)"),d.attr("transform",`translate(${e}, 0)`)),i||(o=o.slice(),o.reverse());for(let t=0;tt&&a>this.parent.parent.layout.width&&(s+=i,e=t,n.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(n)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const Ct={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class Dt{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"!=typeof t.id||!t.id)throw new Error('Panel layouts must specify "id"');if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`);this.id=t.id,this._initialized=!1,this._layout_idx=null,this.svg={},this.layout=at(t||{},Ct),this.parent?(this.state=this.parent.state,this._state_id=this.id,this.state[this._state_id]=this.state[this._state_id]||{}):(this.state=null,this._state_id=null),this.data_layers={},this._data_layer_ids_by_z_index=[],this._data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this._zoom_timeout=null,this._event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this._event_hooks[t]&&this._event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=at(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(gt,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id '${t.id}'; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=xe.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this._data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this._data_layer_ids_by_z_index.length+e.layout.z_index,0)),this._data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this._data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id]._layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){const e=this.data_layers[t];if(!e)throw new Error(`Unable to remove data layer, ID not found: ${t}`);return e.destroyAllTooltips(),e.svg.container&&e.svg.container.remove(),this.layout.data_layers.splice(e._layout_idx,1),delete this.state[e._state_id],delete this.data_layers[t],this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id]._layout_idx=e})),this}clearSelections(){return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height);const{cliparea:t}=this.layout,{margin:e}=this.layout;this.inner_border.attr("x",e.left).attr("y",e.top).attr("width",this.parent_plot.layout.width-(e.left+e.right)).attr("height",this.layout.height-(e.top+e.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const s=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),n=Math.pow(10,e);return t===1/0&&(t=n),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,n),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},i={},a=this.layout.axes;if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};a.x.range&&(t.start=a.x.range.start||t.start,t.end=a.x.range.end||t.end),i.x=[t.start,t.end],i.x_shifted=[t.start,t.end]}if(this.y1_extent){const e={start:t.height,end:0};a.y1.range&&(e.start=a.y1.range.start||e.start,e.end=a.y1.range.end||e.end),i.y1=[e.start,e.end],i.y1_shifted=[e.start,e.end]}if(this.y2_extent){const e={start:t.height,end:0};a.y2.range&&(e.start=a.y2.range.start||e.start,e.end=a.y2.range.end||e.end),i.y2=[e.start,e.end],i.y2_shifted=[e.start,e.end]}let{_interaction:n}=this.parent;const o=n.dragging;if(n.panel_id&&(n.panel_id===this.id||n.linked_panel_ids.includes(this.id))){let a,r=null;if(n.zooming&&"function"==typeof this.x_scale){const s=Math.abs(this.x_extent[1]-this.x_extent[0]),o=Math.round(this.x_scale.invert(i.x_shifted[1]))-Math.round(this.x_scale.invert(i.x_shifted[0]));let r=n.zooming.scale;const l=Math.floor(o*(1/r));r<1&&!isNaN(this.parent.layout.max_region_scale)?r=1/(Math.min(l,this.parent.layout.max_region_scale)/o):r>1&&!isNaN(this.parent.layout.min_region_scale)&&(r=1/(Math.max(l,this.parent.layout.min_region_scale)/o));const h=Math.floor(s*r);a=n.zooming.center-e.left-this.layout.origin.x;const c=a/t.width,d=Math.max(Math.floor(this.x_scale.invert(i.x_shifted[0])-(h-o)*c),1);i.x_shifted=[this.x_scale(d),this.x_scale(d+h)]}else if(o)switch(o.method){case"background":i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x;break;case"x_tick":I.event&&I.event.shiftKey?(i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x):(a=o.start_x-e.left-this.layout.origin.x,r=s(a/(a+o.dragged_x),3),i.x_shifted[0]=0,i.x_shifted[1]=Math.max(t.width*(1/r),1));break;case"y1_tick":case"y2_tick":{const n=`y${o.method[1]}_shifted`;I.event&&I.event.shiftKey?(i[n][0]=t.height+o.dragged_y,i[n][1]=+o.dragged_y):(a=t.height-(o.start_y-e.top-this.layout.origin.y),r=s(a/(a-o.dragged_y),3),i[n][0]=t.height,i[n][1]=t.height-t.height*(1/r))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(i[t][0]),this[`${t}_scale`].invert(i[t][1])],this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!I.event.shiftKey&&!I.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(I.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=I.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,I.event.wheelDelta||-I.event.detail||-I.event.deltaY));0!==e&&(this.parent._interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),n=this.parent._interaction,n.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this._zoom_timeout&&clearTimeout(this._zoom_timeout),this._zoom_timeout=setTimeout((()=>{this.parent._interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this._initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{const e=this.layout.axes[t];Object.keys(e).length&&!1!==e.render?(e.render=!0,e.label=e.label||null):e.render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){const s=this.layout;return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),s.height=Math.max(Math.round(+e),s.min_height)),s.cliparea.width=Math.max(this.parent_plot.layout.width-(s.margin.left+s.margin.right),0),s.cliparea.height=Math.max(s.height-(s.margin.top+s.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",s.height),this._initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this._initialized&&this.render(),this}setMargin(t,e,s,i){let a;const{cliparea:n,margin:o}=this.layout;return!isNaN(t)&&t>=0&&(o.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(o.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(o.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(o.left=Math.max(Math.round(+i),0)),o.top+o.bottom>this.layout.height&&(a=Math.floor((o.top+o.bottom-this.layout.height)/2),o.top-=a,o.bottom-=a),o.left+o.right>this.parent_plot.layout.width&&(a=Math.floor((o.left+o.right-this.parent_plot.layout.width)/2),o.left-=a,o.right-=a),["top","right","bottom","left"].forEach((t=>{o[t]=Math.max(o[t],0)})),n.width=Math.max(this.parent_plot.layout.width-(o.left+o.right),0),n.height=Math.max(this.layout.height-(o.top+o.bottom),0),n.origin.x=o.left,n.origin.y=o.top,this._initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=_t.call(this),this.loader=pt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new Pt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new It(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this._data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(I.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent._panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){const{parent:t}=this,e=this.layout.y_index;return t._panel_ids_by_y_index[e-1]&&(t._panel_ids_by_y_index[e]=t._panel_ids_by_y_index[e-1],t._panel_ids_by_y_index[e-1]=this.id,t.applyPanelYIndexesToPanelLayouts(),t.positionPanels()),this}moveDown(){const{_panel_ids_by_y_index:t}=this.parent;return t[this.layout.y_index+1]&&(t[this.layout.y_index]=t[this.layout.y_index+1],t[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this._data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this._data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this._data_promises).then((()=>{this._initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=I.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=I.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this._data_layer_ids_by_z_index.reduce(((e,a)=>{const n=s.data_layers[a];return e.concat(n.getTicks(t,i))}),[]).map((t=>{let s={};return s=at(s,e),at(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,n=1.5,o=.5+1.5*n,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(_=parseFloat(_.toFixed(c)));u.push(_),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eqt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=I.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=I.select(this).select("text");s[`${t}_ticks`][i].style&>(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const n=this.layout.axes[t].label||null;return null!==n&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(Ht(n,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof I.select(this).node().focus&&I.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";I.event&&I.event.shiftKey&&(i="move"),I.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){I.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this._data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this._data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;Dt.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Dt.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Bt={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Ut{constructor(t,e,s){this._initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this._panel_ids_by_y_index=[],this._remap_promises=[],this.layout=s,at(this.layout,Bt),this._base_layout=nt(this.layout),this.state=this.layout.state,this.lzd=new ut(e),this._external_listeners=new Map,this._event_hooks={},this._interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this._event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this._event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Dt(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this._panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this._panel_ids_by_y_index.length+e.layout.y_index,0)),this._panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this._panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id]._layout_idx=s,this._initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t]._data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i._layer_state,delete this.layout.state[i._state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){const e=this.panels[t];if(!e)throw new Error(`Unable to remove panel, ID not found: ${t}`);return this._panel_boundaries.hide(),this.clearPanelData(t),e.loader.hide(),e.toolbar.destroy(!0),e.curtain.hide(),e.svg.container&&e.svg.container.remove(),this.layout.panels.splice(e._layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id]._layout_idx=e})),this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this._initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e){const{from_layer:s,namespace:i,data_operations:a,onerror:n}=t,o=n||function(t){console.error("An error occurred while acting on an external callback",t)};if(s){const t=`${this.getBaseId()}.`,i=s.startsWith(t)?s:`${t}${s}`;let a=!1;for(let t of Object.values(this.panels))if(a=Object.values(t.data_layers).some((t=>t.getBaseId()===i)),a)break;if(!a)throw new Error(`Could not subscribe to unknown data layer ${i}`);const n=t=>{if(t.data.layer===i)try{e(t.data.content,this)}catch(t){o(t)}};return this.on("data_from_layer",n),n}if(!i)throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option");const[r,l]=this.lzd.config_to_sources(i,a),h=()=>{try{this.lzd.getData(this.state,r,l).then((t=>e(t,this))).catch(o)}catch(t){o(t)}};return this.on("data_rendered",h),h}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this._remap_promises=[],this.loading_data=!0;for(let t in this.panels)this._remap_promises.push(this.panels[t].reMap());return Promise.all(this._remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e._data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this._initialized=!1,this.svg=null,this.panels=null}mutateLayout(){Object.values(this.panels).forEach((t=>{Object.values(t.data_layers).forEach((t=>t.mutateLayout()))}))}_canInteract(t){t=t||null;const{_interaction:e}=this;return t?(void 0===e.panel_id||e.panel_id===t)&&!this.loading_data:!(e.dragging||e.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==I.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this._panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");return this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,n=e.layout.height*s;e.setDimensions(a,n),e.setOrigin(0,i),i+=n,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this._initialized&&(this._panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e of Object.values(this.panels))e.layout.interaction.x_linked&&(t.left=Math.max(t.left,e.layout.margin.left),t.right=Math.max(t.right,e.layout.margin.right));let e=0;return this._panel_ids_by_y_index.forEach((s=>{const i=this.panels[s],a=i.layout;if(i.setOrigin(0,e),e+=this.panels[s].layout.height,a.interaction.x_linked){const e=Math.max(t.left-a.margin.left,0)+Math.max(t.right-a.margin.right,0);a.width+=e,a.margin.left=t.left,a.margin.right=t.right,a.cliparea.origin.x=t.left}})),this.setDimensions(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize){I.select(this.container).classed("lz-container-responsive",!0);const t=()=>window.requestAnimationFrame((()=>this.rescaleSVG()));if(window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t),"undefined"!=typeof IntersectionObserver){const t={root:document.documentElement,threshold:.9};new IntersectionObserver(((t,e)=>{t.some((t=>t.intersectionRatio>0))&&this.rescaleSVG()}),t).observe(this.container)}const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}if(this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this._mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=_t.call(this),this.loader=pt.call(this),this._panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent._panel_ids_by_y_index.forEach(((t,e)=>{const s=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=I.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent._panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+I.event.dy);const i=t.layout.height-s;this.parent._panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent._panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent._panel_boundaries.selectors.push(s)}));const t=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=I.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+I.event.dx,this.parent._total_height+I.event.dy)})),t.call(e),this.parent._panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent._panel_ids_by_y_index[s]],a=i._getPageOrigin(),n=t.x,o=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${o}px`).style("left",`${n}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&I.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this._panel_boundaries.hide_timeout),this._panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this._panel_boundaries.hide_timeout=setTimeout((()=>{this._panel_boundaries.hide()}),300)})),this.toolbar=new Pt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this._mouse_guide.vertical.attr("x",-1),this._mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=I.mouse(this.svg.node());this._mouse_guide.vertical.attr("x",t[0]),this._mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{const{_interaction:t}=this;if(t.dragging){const e=I.mouse(this.svg.node());I.event&&I.event.preventDefault(),t.dragging.dragged_x=e[0]-t.dragging.start_x,t.dragging.dragged_y=e[1]-t.dragging.start_y,this.panels[t.panel_id].render(),t.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=I.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this._initialized=!0;const a=this.svg.node().getBoundingClientRect(),n=a.width?a.width:this.layout.width,o=a.height?a.height:this._total_height;return this.setDimensions(n,o),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof Dt&&s&&this._canInteract()))return this.stopDrag();const i=I.mouse(this.svg.node());return this._interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){const{_interaction:t}=this;if(!t.dragging)return this;if("object"!=typeof this.panels[t.panel_id])return this._interaction={},this;const e=this.panels[t.panel_id],s=(t,s,i)=>{e._data_layer_ids_by_z_index.forEach((a=>{const n=e.data_layers[a].layout[`${t}_axis`];n.axis===s&&(n.floor=i[0],n.ceiling=i[1],delete n.lower_buffer,delete n.upper_buffer,delete n.min_extent,delete n.ticks)}))};switch(t.dragging.method){case"background":case"x_tick":0!==t.dragging.dragged_x&&(s("x",1,e.x_extent),this.applyState({start:e.x_extent[0],end:e.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==t.dragging.dragged_y){const i=parseInt(t.dragging.method[1]);s("y",i,e[`y${i}_extent`])}}return this._interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function qt(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),n=Math.min(Math.max(e,0),2),o=Math.min(Math.max(a,n),12);let r=`${(t/Math.pow(10,e)).toFixed(o)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Ft(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function Ht(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const n=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(n())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},o=[];for(;i.length>0;)o.push(n());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new K(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return o.map(l).join("")}const Gt=new h;Gt.add("=",((t,e)=>t===e)),Gt.add("!=",((t,e)=>t!=e)),Gt.add("<",((t,e)=>tt<=e)),Gt.add(">",((t,e)=>t>e)),Gt.add(">=",((t,e)=>t>=e)),Gt.add("%",((t,e)=>t%e)),Gt.add("in",((t,e)=>e&&e.includes(t))),Gt.add("match",((t,e)=>t&&t.includes(e)));const Jt=Gt,Zt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Kt=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Wt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Yt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let n=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var n=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(n=i)})),null===n)return a;const t=(+e-s[n-1])/(s[n]-s[n-1]);return isFinite(t)?I.interpolate(i[n-1],i[n])(t):a}};function Qt(t,e){if(void 0===e)return null;const{beta_field:s,stderr_beta_field:i,"+":a=null,"-":n=null}=t;if(!s||!i)throw new Error("effect_direction must specify how to find required 'beta' and 'stderr_beta' fields");const o=e[s],r=e[i];if(void 0!==o)if(void 0!==r){if(o-1.96*r>0)return a;if(o+1.96*r<0)return n||null}else{if(o>0)return a;if(o<0)return n}return null}const te=new h;for(let[t,e]of Object.entries(n))te.add(t,e);te.add("if",Zt);const ee=te,se={id:"",type:"",tag:"custom_data_type",namespace:{},data_operations:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class ie{constructor(t,e){this._initialized=!1,this._layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=at(t||{},se),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=nt(this.layout),this.state={},this._state_id=null,this._layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this._tooltips={}),this._global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1},this._data_contract=new Set,this._entities=new Map,this._dependencies=[],this.mutateLayout()}render(){throw new Error("Method must be implemented")}moveForward(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e+1]&&(t[e]=t[e+1],t[e+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e-1]&&(t[e]=t[e-1],t[e-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this._layer_state.extra_fields[i]||(this._layer_state.extra_fields[i]={}),this._layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}mutateLayout(){if(this.parent_plot){const{namespace:t,data_operations:e}=this.layout;this._data_contract=rt(this.layout,Object.keys(t));const[s,i]=this.parent_plot.lzd.config_to_sources(t,e,this);this._entities=s,this._dependencies=i}}_getDataExtent(t,e){return t=t||this.data,I.extent(t,(t=>+new K(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field;let i=t[s];if(void 0===i&&/{{[^{}]*}}/.test(s)&&(i=Ht(s,t,{})),null==i)throw new Error("Unable to generate element ID");const a=i.toString().replace(/\W/g,""),n=`${this.getBaseId()}-${a}`.replace(/([:.[\],])/g,"_");return t[e]=n,n}getElementStatusNodeId(t){return null}getElementById(t){const e=I.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Jt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new K(t):null;if(this.data.length&&this._data_contract.size){const t=new Set(this._data_contract);for(let e of this.data)if(Object.keys(e).forEach((e=>t.delete(e))),!t.size)break;t.size&&console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...t]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`)}return this.data.forEach(((a,n)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(p+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=_<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-_,0),e=Math.max(c.width/2+_-u,0);y=h.x+_-c.width/2-e+t,b=h.x+_-y-7,"top"===w?(g=h.y+p-(v+c.height+8),f="down",m=c.height-1):(g=h.y+p+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+_+x+8,f="left",b=-8):(y=h.x+_-c.width-x-8,f="right",b=c.width-1),p-c.height/2<=0?(g=h.y+p-10.5-6,m=6):p+c.height/2>=d?(g=h.y+p+7+6-c.height,m=c.height-14-6):(g=h.y+p-c.height/2,m=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${f}`).style("left",`${b}px`).style("top",`${m}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:n}=t,o=Jt.get(i),r=this.getElementAnnotation(e);o(s?new K(s).resolve(e,r):e,n)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this._layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;C.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this._state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this._state_id]=t),this._layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this._tooltips[e])return this._tooltips[e]={data:t,arrow:null,selector:I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this._layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this._tooltips[e].selector.html(""),this._tooltips[e].arrow=null,this.layout.tooltip.html&&this._tooltips[e].selector.html(Ht(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this._tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this._tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this._tooltips[s]&&("object"==typeof this._tooltips[s].selector&&this._tooltips[s].selector.remove(),delete this._tooltips[s]),!e){this._layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this._tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this._tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this._tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this._tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){const s=this.layout.tooltip;if("object"!=typeof s)return this;const i=this.getElementId(t),a=(t,e,s)=>{let i=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",i=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let n;for(let o in e)n=a(t,e[o],o),null===i?i=n:"and"===s?i=i&&n:"or"===s&&(i=i||n)}}return i};let n={};"string"==typeof s.show?n={and:[s.show]}:"object"==typeof s.show&&(n=s.show);let o={};"string"==typeof s.hide?o={and:[s.hide]}:"object"==typeof s.hide&&(o=s.hide);const r=this._layer_state;var l={};C.adjectives.forEach((t=>{const e=`un${t}`;l[t]=r.status_flags[t].has(i),l[e]=!l[t]}));const h=a(l,n),c=a(l,o),d=r.status_flags.has_tooltip.has(i);return!h||!e&&!d||c?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),I.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const n=this.getElementStatusNodeId(e);null!==n&&I.select(`#${n}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const o=!this._layer_state.status_flags[t].has(a);s&&o&&this._layer_state.status_flags[t].add(a),s||o||this._layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,o),o&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!o&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!o&&s||this.parent.emit("match_requested",{value:new K(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this._layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this._layer_state.status_flags[t]=new Set}return this._global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||I.select(I.event.target).datum(),s===!!I.event.ctrlKey&&i===!!I.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a._layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=Ht(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this._layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e._state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this._entities,this._dependencies).then((t=>{this.data=t,this.applyDataMethods(),this._initialized=!0,this.parent.emit("data_from_layer",{layer:this.getBaseId(),content:nt(t)},!0)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;ie.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},ie.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},ie.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},ie.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const ae={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class ne extends ie{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");at(t,ae),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],n=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+n)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const oe={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class re extends ie{constructor(t){if(at(t,oe),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions.length&&t.namespace&&Object.keys(t.namespace).length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>I.ascending(t[s],e[s])||I.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,n){const o=a[a.length-1]||t;if(t[s]===o[s]&&t[i]<=o[e]){const s=Math.min(o[i],t[i]),n=Math.max(o[e],t[e]);t=Object.assign({},o,t,{[i]:s,[e]:n}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const le={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class he extends ie{constructor(t){t=at(t,le),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function n(t){const a=t[e.x_axis.field1],n=t[e.x_axis.field2],o=(a+n)/2,r=[[s(a),i(0)],[s(o),i(t[e.y_axis.field])],[s(n),i(0)]];return I.line().x((t=>t[0])).y((t=>t[1])).curve(I.curveNatural)(r)}const o=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(gt,e.style),o.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(o).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>n(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>n(t))),r.exit().remove(),o.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],n=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:n(t.data[s.y_axis.field]),y_max:n(0)}}}const ce={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:15,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class de extends ie{constructor(t){t=at(t,ce),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=I.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const n=I.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,n.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(n).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),n.exit().remove();const o=I.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));o.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(o).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),o.exit().remove();const r=I.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=I.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=I.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const ue={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5,tooltip:null};class _e extends ie{constructor(t){if((t=at(t,ue)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const n=t.x_scale,o=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?I.area().x((t=>+n(t[e]))).y0(+o(0)).y1((t=>+o(t[s]))):I.line().x((t=>+n(t[e]))).y((t=>+o(t[s]))).curve(I[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(gt,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;void 0===e&&(e=!0),this._global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this._global_statuses).forEach((t=>{this._global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const pe={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class ge extends ie{constructor(t){t=at(t,pe),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments)}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],n=I.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const n=+t[e](s.y);return isNaN(n)?a[i]:n}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",n).call(gt,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=I.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ye={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class fe extends ie{constructor(t){(t=at(t,ye)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),n=Math.sqrt(a/Math.PI);return{x_min:e-n,x_max:e+n,y_min:i-n,y_max:i+n}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,n=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,o=(t,a)=>{const n=+t.attr("x"),o=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",n-o),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",n+o),i&&a.attr("x2",r+l))};t._label_texts.each((function(e,a){const r=I.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>n){const e=i?I.select(t._label_lines.nodes()[a]):null;o(r,e)}})),t._label_texts.each((function(e,n){const r=I.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?I.select(t._label_lines.nodes()[n]):null;t._label_texts.each((function(){const t=I.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(o(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=o.attr("y"),c=.5*(r.topp?(g=d-+n,d=+n,u-=g):u+l.height/2>p&&(g=u-+h,u=+h,d-=g),a.attr("y",d),o.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t._label_texts.nodes();t._label_lines.attr("y2",((t,s)=>I.select(e[s]).attr("y")))}this._label_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let n=this._applyFilters();if(n.forEach((t=>{let n=e(t[this.layout.x_axis.field]),o=s(t[this.layout.y_axis.field]);isNaN(n)&&(n=-1e3),isNaN(o)&&(o=-1e3),t[i]=n,t[a]=o})),this.layout.coalesce.active&&n.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:o,x_gap:r,y_gap:l}=this.layout.coalesce;n=function(t,e,s,i,a,n,o){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function _(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function p(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],f=g>=e&&g<=s&&y>=a&&y<=n;t.lz_is_match||!f?(_(),r.push(t)):null===c?p(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=o?u.push(t):(_(),p(g,y,t))})),_(),r}(n,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(o)?s(+o):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=n.filter(t)}else e=n;this._label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const o=`lz-data_layer-${this.layout.type}-label`,r=this._label_groups.enter().append("g").attr("class",o);this._label_texts&&this._label_texts.remove(),this._label_texts=this._label_groups.merge(r).append("text").text((e=>Ht(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(gt,t.layout.label.style||{}),t.layout.label.lines&&(this._label_lines&&this._label_lines.remove(),this._label_lines=this._label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(gt,t.layout.label.lines.style||{})),this._label_groups.exit().remove()}else this._label_texts&&this._label_texts.remove(),this._label_lines&&this._label_lines.remove(),this._label_groups&&this._label_groups.remove();const o=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(n,(t=>t[this.layout.id_field])),r=I.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>ot(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;o.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(o).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),o.exit().remove(),this.layout.label&&(this.flip_labels(),this._label_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=I.select(I.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class me extends fe{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],n="string"==typeof i?i.toLowerCase():i,o="string"==typeof a?a.toLowerCase():a;return n===o?0:n{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],n=i[e],o=s[a]||[n,n];s[a]=[Math.min(o[0],n),Math.max(o[1],n)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const o=i[t];let r;switch(s){case"left":r=o[0];break;case"center":const t=o[1]-o[0];r=o[0]+(0!==t?t:o[0])/2;break;case"right":r=o[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const be=new c;for(let[t,e]of Object.entries(o))be.add(t,e);const xe=be,ve=7.301,we={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{assoc:variant|htmlescape}}
          \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
          \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
          \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
          '},$e=function(){const t=nt(we);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),ze={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

          {{gene_name|htmlescape}}

          Gene ID: {{gene_id|htmlescape}}
          Transcript ID: {{transcript_id|htmlescape}}
          {{#if pLI}}
          ConstraintExpected variantsObserved variantsConst. Metric
          Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
          o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
          Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
          o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
          pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
          o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

          {{/if}}More data on gnomAD'},ke={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{catalog:variant|htmlescape}}
          Catalog entries: {{n_catalog_matches|htmlescape}}
          Top Trait: {{catalog:trait|htmlescape}}
          Top P Value: {{catalog:log_pvalue|logtoscinotation}}
          More: GWAS catalog / dbSNP'},Ee={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
          {{access:start1|htmlescape}}-{{access:end1|htmlescape}}
          Promoter
          {{access:start2|htmlescape}}-{{access:end2|htmlescape}}
          {{#if access:target}}Target: {{access:target|htmlescape}}
          {{/if}}Score: {{access:score|htmlescape}}"},Me={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:ve},Se={id:"recombrate",namespace:{recomb:"recomb"},data_operations:[{type:"fetch",from:["recomb"]}],type:"line",tag:"recombination",z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"recomb:position"},y_axis:{axis:2,field:"recomb:recomb_rate",floor:0,ceiling:100}},Ne={namespace:{assoc:"assoc",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)"]},{type:"left_match",name:"assoc_plus_ld",requires:["assoc","ld"],params:["assoc:position","ld:position2"]}],id:"associationpvalues",type:"scatter",tag:"association",id_field:"assoc:variant",coalesce:{active:!0},point_shape:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"diamond"}},{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"assoc:beta",stderr_beta_field:"assoc:se"}},"circle"],point_size:{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:80,else:40}},color:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"#9632b8"}},{scale_function:"numerical_bin",field:"ld:correlation",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{label:"LD (r²)",label_size:14},{shape:"ribbon",orientation:"vertical",width:10,height:15,color_stops:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"],tick_labels:[0,.2,.4,.6,.8,1]}],label:null,z_index:2,x_axis:{field:"assoc:position"},y_axis:{axis:1,field:"assoc:log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(we)},Ae={id:"coaccessibility",type:"arcs",tag:"coaccessibility",namespace:{access:"access"},data_operations:[{type:"fetch",from:["access"]}],match:{send:"access:target",receive:"access:target"},id_field:"{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}",filters:[{field:"access:score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"access:start1",field2:"access:start2"},y_axis:{axis:1,field:"access:score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(Ee)},Oe=function(){let t=nt(Ne);return t=at({id:"associationpvaluescatalog",fill_opacity:.7},t),t.data_operations.push({type:"assoc_to_gwas_catalog",name:"assoc_catalog",requires:["assoc_plus_ld","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}),t.tooltip.html+='{{#if catalog:rsid}}
          See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t}(),Te={id:"phewaspvalues",type:"category_scatter",tag:"phewas",namespace:{phewas:"phewas"},data_operations:[{type:"fetch",from:["phewas"]}],point_shape:[{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"phewas:beta",stderr_beta_field:"phewas:se"}},"circle"],point_size:70,tooltip_positioning:"vertical",id_field:"{{phewas:trait_group}}_{{phewas:trait_label}}",x_axis:{field:"lz_auto_x",category_field:"phewas:trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"phewas:log_pvalue",floor:0,upper_buffer:.15},color:[{field:"phewas:trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Trait: {{phewas:trait_label|htmlescape}}
          \nTrait Category: {{phewas:trait_group|htmlescape}}
          \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
          β: {{phewas:beta|scinotation|htmlescape}}
          {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}"},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{phewas:trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"phewas:log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},Le={namespace:{gene:"gene",constraint:"constraint"},data_operations:[{type:"fetch",from:["gene","constraint(gene)"]},{name:"gene_constraint",type:"genes_to_gnomad_constraint",requires:["gene","constraint"]}],id:"genes",type:"genes",tag:"genes",id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ze)},je=at({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},nt(Le)),Pe={namespace:{assoc:"assoc",catalog:"catalog"},data_operations:[{type:"fetch",from:["assoc","catalog"]},{type:"assoc_to_gwas_catalog",name:"assoc_plus_ld",requires:["assoc","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}],id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:"#0000CC",filters:[{field:"catalog:rsid",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ke),tooltip_positioning:"top"},Re={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},Ie={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},Ce={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},De={widgets:[{type:"title",title:"LocusZoom",subtitle:`v${l}`,position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Be=function(){const t=nt(De);return t.widgets.push(nt(Re)),t}(),Ue=function(){const t=nt(De);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),qe={id:"association",tag:"association",min_height:200,height:300,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:50},y2:{label:"Recombination Rate (cM/Mb)",label_offset:46}},legend:{orientation:"vertical",origin:{x:75,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Me),nt(Se),nt(Ne)]},Fe={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:40,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Ae)]},He=function(){let t=nt(qe);return t=at({id:"associationcatalog"},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{catalog:trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"catalog:trait",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve},{field:"ld:correlation",operator:">",value:.4}],style:{"font-size":"12px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[nt(Me),nt(Se),nt(Oe)],t}(),Ge={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:55,bottom:20,left:70},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},nt(Ie)),t}(),data_layers:[nt(je)]},Je={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:55,bottom:120,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:50}},data_layers:[nt(Me),nt(Te)]},Ze={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:55,bottom:10,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Pe)]},Ke={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[nt(qe),nt(Ge)]},Ve={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[Ze,He,Ge]},We={width:800,responsive_resize:!0,toolbar:De,panels:[nt(Je),at({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"}}},nt(Ge))],mouse_guide:!1},Ye={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:nt(De),panels:[nt(Fe),function(){const t=Object.assign({height:270},nt(Ge)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},Xe={standard_association:we,standard_association_with_label:$e,standard_genes:ze,catalog_variant:ke,coaccessibility:Ee},Qe={ldlz2_pop_selector:Re,gene_selector_menu:Ie},ts={standard_panel:Ce,standard_plot:De,standard_association:Be,region_nav_plot:Ue},es={significance:Me,recomb_rate:Se,association_pvalues:Ne,coaccessibility:Ae,association_pvalues_catalog:Oe,phewas_pvalues:Te,genes:Le,genes_filtered:je,annotation_catalog:Pe},ss={association:qe,coaccessibility:Fe,association_catalog:He,genes:Ge,phewas:Je,annotation_catalog:Ze},is={standard_association:Ke,association_catalog:Ve,standard_phewas:We,coaccessibility:Ye};const as=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);const a=s.namespace;i.namespace||delete s.namespace;let n=at(s,i);return a&&(n=it(n,a)),nt(n)}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=nt(s);return"data_layer"===t&&a.namespace&&(a._auto_fields=[...rt(a,Object.keys(a.namespace))].sort()),super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return at(t,e)}renameField(){return lt(...arguments)}mutate_attrs(){return ht(...arguments)}query_attrs(){return ct(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))as.add(t,s,i);const ns=as,os=new h;function rs(t){return(e,s,...i)=>{if(2!==s.length)throw new Error("Join functions must receive exactly two recordsets");return t(...s,...i)}}os.add("left_match",rs(x)),os.add("inner_match",rs((function(t,e,s,i){return b("inner",...arguments)}))),os.add("full_outer_match",rs((function(t,e,s,i){return b("outer",...arguments)}))),os.add("assoc_to_gwas_catalog",rs((function(t,e,s,i,a){if(!t.length)return t;const n=m(e,i),o=[];for(let t of n.values()){let e,s=0;for(let i of t){const t=i[a];t>=s&&(e=i,s=t)}e.n_catalog_matches=t.length,o.push(e)}return x(t,o,s,i)}))),os.add("genes_to_gnomad_constraint",rs((function(t,e){return t.forEach((function(t){const s=`_${t.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=e[s]&&e[s].gnomad_constraint;i&&Object.keys(i).forEach((function(e){let s=i[e];void 0===t[e]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),t[e]=s)}))})),t})));const ls=os;const hs={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return I.select(t).html(""),I.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!I.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Ut(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Ft(s[2]),e=Ft(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Ft(s[2]),end:Ft(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Ft(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=I.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(gt,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||R}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:R,DataLayers:xe,DataFunctions:ls,Layouts:ns,MatchFunctions:Jt,ScaleFunctions:ee,TransformationFunctions:Z,Widgets:jt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),R}},cs=[];hs.use=function(t,...e){if(!cs.includes(t)){if(e.unshift(hs),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}cs.push(t)}};const ds=hs})(),LocusZoom=i.default})(); //# sourceMappingURL=locuszoom.app.min.js.map \ No newline at end of file diff --git a/dist/locuszoom.app.min.js.map b/dist/locuszoom.app.min.js.map index fb87332e..40090508 100644 --- a/dist/locuszoom.app.min.js.map +++ b/dist/locuszoom.app.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/./node_modules/@hapi/hoek/lib/assert.js","webpack://[name]/./node_modules/@hapi/hoek/lib/error.js","webpack://[name]/./node_modules/@hapi/hoek/lib/stringify.js","webpack://[name]/./node_modules/@hapi/topo/lib/index.js","webpack://[name]/./node_modules/just-clone/index.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./esm/data/undercomplicate/lru_cache.js","webpack://[name]/./esm/data/undercomplicate/util.js","webpack://[name]/./esm/data/undercomplicate/requests.js","webpack://[name]/./esm/data/undercomplicate/joins.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./esm/data/undercomplicate/adapter.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/registry/data_ops.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["AssertError","module","exports","condition","args","length","Error","Stringify","super","filter","arg","map","message","join","captureStackTrace","this","assert","JSON","stringify","apply","err","Assert","internals","_items","nodes","options","before","concat","after","group","sort","includes","Array","isArray","node","item","seq","push","manual","valid","_sort","others","other","Object","assign","mergeSort","i","graph","graphAfters","create","groups","expandedGroups","graphNodeItem","ancestors","children","child","visited","sorted","next","j","shouldSeeCount","seenCount","k","seqIndex","value","sortedItem","a","b","getRegExpFlags","regExp","source","flags","global","ignoreCase","multiline","sticky","unicode","clone","obj","result","key","type","toString","call","slice","Date","getTime","RegExp","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","prop","prototype","hasOwnProperty","r","Symbol","toStringTag","RegistryBase","Map","name","has","override","set","delete","from","keys","ClassRegistry","parent_name","source_name","overrides","console","warn","arguments","base","sub","add","LLNode","metadata","prev","LRUCache","max_size","_max_size","_cur_size","_store","_head","_tail","cached","prior","_remove","old","match_callback","data","getLinkedData","shared_options","entities","dependencies","consolidate","parsed","spec","exec","name_alone","name_deps","deps","split","_parse_declaration","dag","toposort","entries","e","order","responses","provider","depends_on","this_result","Promise","all","then","prior_results","_provider_name","getData","values","all_results","groupBy","records","group_key","item_group","_any_match","left","right","left_key","right_key","right_index","results","left_match_value","right_matches","right_item","left_index","right_match_value","left_match","REGEX_MARKER","parseMarker","test","match","BaseApiAdapter","BaseLZAdapter","config","_config","cache_enabled","cache_size","_enable_cache","_cache","dependent_data","response_text","_buildRequestOptions","cache_key","_getCacheKey","resolve","_performRequest","text","_normalizeResponse","_cache_meta","catch","remove","_annotateRecords","_postProcessResponse","_url","url","_getURL","fetch","response","ok","statusText","parse","params","prefix_namespace","limit_fields","_prefix_namespace","_limit_fields","Set","chr","start","end","superset","find","md","row","reduce","acc","label","a_record","fieldname","suffixer","BaseUMAdapter","_genome_build","genome_build","build","constructor","N","every","fields","record","AssociationLZ","_source_id","request_options","GwasCatalogLZ","_validateBuildSource","source_query","GeneLZ","GeneConstraintLZ","state","genes_data","unique_gene_names","gene","gene_name","query","replace","body","method","headers","LDServer","assoc_data","assoc_variant_name","_findPrefixedKey","assoc_logp_name","refvar","best_hit","ldrefvar","best_logp","variant","log_pvalue","lz_is_ld_refvar","chrom","pos","ref","alt","coord","String","__find_ld_refvar","_skip_request","ld_refvar","ld_source","ld_population","ld_pop","population","encodeURIComponent","combined","chainRequests","payload","forEach","RecombLZ","StaticSource","_data","PheWASLZ","registry","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","transforms","full_name","field_name","transformations","val","transform","extra","undefined","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","m","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","paths","p","_","__","subject","uniqPaths","arr","elem","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","layout","shared_namespaces","requested_ns","merge","custom_layout","default_layout","property","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","findFields","prefixes","field_finder","all_ns","value_type","a_match","renameField","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","regex","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","DataOperation","join_type","initiator","_callable","_initiator","_params","plot_state","dependent_recordsets","data_layer","sources","_sources","namespace_options","data_operations","namespace_local_names","dependency_order","unshift","ns_pattern","local_name","global_name","dep_spec","requires","namecount","require_name","task","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","parentNode","insert","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","x","width","delay","setTimeout","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","position","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","index","indexOf","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","element","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","_panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","radioId","has_option","choice","defaultName","default_config_display_name","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","addWidget","widget","error","_panel_boundaries","dragging","_interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","line_height","_data_layer_ids_by_z_index","reverse","layer_legend","label_x","label_y","shape_factory","path_y","is_horizontal","color_stops","all_elements","ribbon_group","axis_group","axis_offset","tick_labels","range","scale","domain","axis","tickSize","tickValues","tickFormat","v","to_next_marking","radius","PI","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","_initialized","_layout_idx","_state_id","_data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","_zoom_timeout","_event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","parseFloat","y_axis","z_index","dlid","idx","layout_idx","data_layer_layout","target_layer","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","axes_config","base_x_range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","current_drag","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","namespace","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tick_format","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","panel_boundaries","mouse_guide","Plot","datasource","_remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","_layer_state","_setDefaultState","target_panel","clearPanelData","opts","success_callback","from_layer","onerror","error_callback","base_prefix","layer_target","startsWith","is_valid_layer","some","listener","config_to_sources","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","mutateLayout","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","resize_listener","rescaleSVG","window","addEventListener","trackExternalListener","IntersectionObserver","threshold","observer","entry","intersectionRatio","observe","load_listener","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","_mouse_guide","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","ret","positionStringToInt","suffixre","mult","tokens","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","field_value","numerical_bin","breaks","null_value","curr","categorical_bin","categories","ordinal_cycle","stable_choice","max_cache_size","clear","hash","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","effect_direction","input","beta_field","stderr_beta_field","plus_result","neg_result","beta_val","se_val","id_field","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","_tooltips","_global_statuses","_data_contract","_entities","_dependencies","layer_order","current_index","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","fields_unseen","debug","lz_is_match","getDataLayer","getPanel","getPlot","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","tooltip_layout","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","_label_texts","da","dal","_label_lines","dax","abound","bbound","_label_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","_label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","version","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","custom_namespaces","_auto_fields","contents","list","_wrap_join","handle","catalog_data","assoc_key","catalog_key","catalog_logp_name","catalog_by_variant","catalog_flat","claims","best_variant","best","n_catalog_matches","constraint_data","alias","constraint","LocusZoom","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","source_id","Adapters","DataLayers","DataFunctions","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["'use strict';\n\nconst AssertError = require('./error');\n\nconst internals = {};\n\n\nmodule.exports = function (condition, ...args) {\n\n if (condition) {\n return;\n }\n\n if (args.length === 1 &&\n args[0] instanceof Error) {\n\n throw args[0];\n }\n\n throw new AssertError(args);\n};\n","'use strict';\n\nconst Stringify = require('./stringify');\n\n\nconst internals = {};\n\n\nmodule.exports = class extends Error {\n\n constructor(args) {\n\n const msgs = args\n .filter((arg) => arg !== '')\n .map((arg) => {\n\n return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);\n });\n\n super(msgs.join(' ') || 'Unknown error');\n\n if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$\n Error.captureStackTrace(this, exports.assert);\n }\n }\n};\n","'use strict';\n\nconst internals = {};\n\n\nmodule.exports = function (...args) {\n\n try {\n return JSON.stringify.apply(null, args);\n }\n catch (err) {\n return '[Cannot display object: ' + err.message + ']';\n }\n};\n","'use strict';\n\nconst Assert = require('@hapi/hoek/lib/assert');\n\n\nconst internals = {};\n\n\nexports.Sorter = class {\n\n constructor() {\n\n this._items = [];\n this.nodes = [];\n }\n\n add(nodes, options) {\n\n options = options || {};\n\n // Validate rules\n\n const before = [].concat(options.before || []);\n const after = [].concat(options.after || []);\n const group = options.group || '?';\n const sort = options.sort || 0; // Used for merging only\n\n Assert(!before.includes(group), `Item cannot come before itself: ${group}`);\n Assert(!before.includes('?'), 'Item cannot come before unassociated items');\n Assert(!after.includes(group), `Item cannot come after itself: ${group}`);\n Assert(!after.includes('?'), 'Item cannot come after unassociated items');\n\n if (!Array.isArray(nodes)) {\n nodes = [nodes];\n }\n\n for (const node of nodes) {\n const item = {\n seq: this._items.length,\n sort,\n before,\n after,\n group,\n node\n };\n\n this._items.push(item);\n }\n\n // Insert event\n\n if (!options.manual) {\n const valid = this._sort();\n Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');\n }\n\n return this.nodes;\n }\n\n merge(others) {\n\n if (!Array.isArray(others)) {\n others = [others];\n }\n\n for (const other of others) {\n if (other) {\n for (const item of other._items) {\n this._items.push(Object.assign({}, item)); // Shallow cloned\n }\n }\n }\n\n // Sort items\n\n this._items.sort(internals.mergeSort);\n for (let i = 0; i < this._items.length; ++i) {\n this._items[i].seq = i;\n }\n\n const valid = this._sort();\n Assert(valid, 'merge created a dependencies error');\n\n return this.nodes;\n }\n\n sort() {\n\n const valid = this._sort();\n Assert(valid, 'sort created a dependencies error');\n\n return this.nodes;\n }\n\n _sort() {\n\n // Construct graph\n\n const graph = {};\n const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives\n const groups = Object.create(null);\n\n for (const item of this._items) {\n const seq = item.seq; // Unique across all items\n const group = item.group;\n\n // Determine Groups\n\n groups[group] = groups[group] || [];\n groups[group].push(seq);\n\n // Build intermediary graph using 'before'\n\n graph[seq] = item.before;\n\n // Build second intermediary graph with 'after'\n\n for (const after of item.after) {\n graphAfters[after] = graphAfters[after] || [];\n graphAfters[after].push(seq);\n }\n }\n\n // Expand intermediary graph\n\n for (const node in graph) {\n const expandedGroups = [];\n\n for (const graphNodeItem in graph[node]) {\n const group = graph[node][graphNodeItem];\n groups[group] = groups[group] || [];\n expandedGroups.push(...groups[group]);\n }\n\n graph[node] = expandedGroups;\n }\n\n // Merge intermediary graph using graphAfters into final graph\n\n for (const group in graphAfters) {\n if (groups[group]) {\n for (const node of groups[group]) {\n graph[node].push(...graphAfters[group]);\n }\n }\n }\n\n // Compile ancestors\n\n const ancestors = {};\n for (const node in graph) {\n const children = graph[node];\n for (const child of children) {\n ancestors[child] = ancestors[child] || [];\n ancestors[child].push(node);\n }\n }\n\n // Topo sort\n\n const visited = {};\n const sorted = [];\n\n for (let i = 0; i < this._items.length; ++i) { // Looping through item.seq values out of order\n let next = i;\n\n if (ancestors[i]) {\n next = null;\n for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values\n if (visited[j] === true) {\n continue;\n }\n\n if (!ancestors[j]) {\n ancestors[j] = [];\n }\n\n const shouldSeeCount = ancestors[j].length;\n let seenCount = 0;\n for (let k = 0; k < shouldSeeCount; ++k) {\n if (visited[ancestors[j][k]]) {\n ++seenCount;\n }\n }\n\n if (seenCount === shouldSeeCount) {\n next = j;\n break;\n }\n }\n }\n\n if (next !== null) {\n visited[next] = true;\n sorted.push(next);\n }\n }\n\n if (sorted.length !== this._items.length) {\n return false;\n }\n\n const seqIndex = {};\n for (const item of this._items) {\n seqIndex[item.seq] = item;\n }\n\n this._items = [];\n this.nodes = [];\n\n for (const value of sorted) {\n const sortedItem = seqIndex[value];\n this.nodes.push(sortedItem.node);\n this._items.push(sortedItem);\n }\n\n return true;\n }\n};\n\n\ninternals.mergeSort = (a, b) => {\n\n return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);\n};\n","module.exports = clone;\n\n/*\n Deep clones all properties except functions\n\n var arr = [1, 2, 3];\n var subObj = {aa: 1};\n var obj = {a: 3, b: 5, c: arr, d: subObj};\n var objClone = clone(obj);\n arr.push(4);\n subObj.bb = 2;\n obj; // {a: 3, b: 5, c: [1, 2, 3, 4], d: {aa: 1}}\n objClone; // {a: 3, b: 5, c: [1, 2, 3], d: {aa: 1, bb: 2}}\n*/\n\nfunction clone(obj) {\n if (typeof obj == 'function') {\n return obj;\n }\n var result = Array.isArray(obj) ? [] : {};\n for (var key in obj) {\n // include prototype properties\n var value = obj[key];\n var type = {}.toString.call(value).slice(8, -1);\n if (type == 'Array' || type == 'Object') {\n result[key] = clone(value);\n } else if (type == 'Date') {\n result[key] = new Date(value.getTime());\n } else if (type == 'RegExp') {\n result[key] = RegExp(value.source, getRegExpFlags(value));\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction getRegExpFlags(regExp) {\n if (typeof regExp.source.flags == 'string') {\n return regExp.source.flags;\n } else {\n var flags = [];\n regExp.global && flags.push('g');\n regExp.ignoreCase && flags.push('i');\n regExp.multiline && flags.push('m');\n regExp.sticky && flags.push('y');\n regExp.unicode && flags.push('u');\n return flags.join('');\n }\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.14.0-beta.3';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","/**\n * Implement an LRU Cache\n */\n\n\nclass LLNode {\n /**\n * A single node in the linked list. Users will only need to deal with this class if using \"approximate match\" (`cache.find()`)\n * @memberOf module:undercomplicate\n * @param {string} key\n * @param {*} value\n * @param {object} metadata\n * @param {LLNode} prev\n * @param {LLNode} next\n */\n constructor(key, value, metadata = {}, prev = null, next = null) {\n this.key = key;\n this.value = value;\n this.metadata = metadata;\n this.prev = prev;\n this.next = next;\n }\n}\n\nclass LRUCache {\n /**\n * Least-recently used cache implementation, with \"approximate match\" semantics\n * @memberOf module:undercomplicate\n * @param {number} [max_size=3]\n */\n constructor(max_size = 3) {\n this._max_size = max_size;\n this._cur_size = 0; // replace with map.size so we aren't managing manually?\n this._store = new Map();\n\n // Track LRU state\n this._head = null;\n this._tail = null;\n\n // Validate options\n if (max_size === null || max_size < 0) {\n throw new Error('Cache \"max_size\" must be >= 0');\n }\n }\n\n /**\n * Check key membership without updating LRU\n * @param key\n * @returns {boolean}\n */\n has(key) {\n return this._store.has(key);\n }\n\n /**\n * Retrieve value from cache (if present) and update LRU cache to say an item was recently used\n * @param key\n * @returns {null|*}\n */\n get(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return null;\n }\n if (this._head !== cached) {\n // Rewrite the cached value to ensure it is head of the list\n this.add(key, cached.value);\n }\n return cached.value;\n }\n\n /**\n * Add an item. Forcibly replaces the existing cached value for the same key.\n * @param key\n * @param value\n * @param {Object} [metadata={}) Metadata associated with an item. Metadata can be used for lookups (`cache.find`) to test for a cache hit based on non-exact match\n */\n add(key, value, metadata = {}) {\n if (this._max_size === 0) {\n // Don't cache items if cache has 0 size.\n return;\n }\n\n const prior = this._store.get(key);\n if (prior) {\n this._remove(prior);\n }\n\n const node = new LLNode(key, value, metadata, null, this._head);\n\n if (this._head) {\n this._head.prev = node;\n } else {\n this._tail = node;\n }\n\n this._head = node;\n this._store.set(key, node);\n\n if (this._max_size >= 0 && this._cur_size >= this._max_size) {\n const old = this._tail;\n this._tail = this._tail.prev;\n this._remove(old);\n }\n this._cur_size += 1;\n }\n\n\n // Cache manipulation methods\n clear() {\n this._head = null;\n this._tail = null;\n this._cur_size = 0;\n this._store = new Map();\n }\n\n // Public method, remove by key\n remove(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return false;\n }\n this._remove(cached);\n return true;\n }\n\n // Internal implementation, useful when working on list\n _remove(node) {\n if (node.prev !== null) {\n node.prev.next = node.next;\n } else {\n this._head = node.next;\n }\n\n if (node.next !== null) {\n node.next.prev = node.prev;\n } else {\n this._tail = node.prev;\n }\n this._store.delete(node.key);\n this._cur_size -= 1;\n }\n\n /**\n * Find a matching item in the cache, or return null. This is useful for \"approximate match\" semantics,\n * to check if something qualifies as a cache hit despite not having the exact same key.\n * (Example: zooming into a region, where the smaller region is a subset of something already cached)\n * @param {function} match_callback A function to be used to test the node as a possible match (returns true or false)\n * @returns {null|LLNode}\n */\n find(match_callback) {\n let node = this._head;\n while (node) {\n const next = node.next;\n if (match_callback(node)) {\n return node;\n }\n node = next;\n }\n }\n}\n\nexport { LRUCache };\n","/**\n * @private\n */\n\nimport justclone from 'just-clone';\n\n/**\n * The \"just-clone\" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.\n * @param {object} data\n * @returns {*}\n */\nfunction clone(data) {\n if (typeof data !== 'object') {\n return data;\n }\n return justclone(data);\n}\n\nexport { clone };\n","/**\n * Perform a series of requests, respecting order of operations\n * @private\n */\n\nimport {Sorter} from '@hapi/topo';\n\n\nfunction _parse_declaration(spec) {\n // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.\n const parsed = /^(?\\w+)$|((?\\w+)+\\(\\s*(?[^)]+?)\\s*\\))/.exec(spec);\n if (!parsed) {\n throw new Error(`Unable to parse dependency specification: ${spec}`);\n }\n\n let {name_alone, name_deps, deps} = parsed.groups;\n if (name_alone) {\n return [name_alone, []];\n }\n\n deps = deps.split(/\\s*,\\s*/);\n return [name_deps, deps];\n}\n\n/**\n * Perform a request for data from a set of providers, taking into account dependencies between requests.\n * This can be a mix of network requests or other actions (like join tasks), provided that the provider be some\n * object that implements a method `instance.getData`\n *\n * Each data layer in LocusZoom will translate the internal configuration into a format used by this function.\n * This function is never called directly in custom user code. In locuszoom, Requester Handles You\n *\n * TODO Future: It would be great to add a warning if the final element in the DAG does not reference all dependencies. This is a limitation of the current toposort library we use.\n *\n * @param {object} shared_options Options passed globally to all requests. In LocusZoom, this is often a copy of \"plot.state\"\n * @param {Map} entities A lookup of named entities that implement the method `instance.getData -> Promise`\n * @param {String[]} dependencies A description of how to fetch entities, and what they depend on, like `['assoc', 'ld(assoc)']`.\n * **Order will be determined by a DAG and the last item in the DAG is all that is returned.**\n * @param {boolean} [consolidate=true] Whether to return all results (almost never used), or just the last item in the resolved DAG.\n * This can be a pitfall in common usage: if you forget a \"join/consolidate\" task, the last result may appear to be missing some data.\n * @returns {Promise}\n */\nfunction getLinkedData(shared_options, entities, dependencies, consolidate = true) {\n if (!dependencies.length) {\n return [];\n }\n\n const parsed = dependencies.map((spec) => _parse_declaration(spec));\n const dag = new Map(parsed);\n\n // Define the order to perform requests in, based on a DAG\n const toposort = new Sorter();\n for (let [name, deps] of dag.entries()) {\n try {\n toposort.add(name, {after: deps, group: name});\n } catch (e) {\n throw new Error(`Invalid or possible circular dependency specification for: ${name}`);\n }\n }\n const order = toposort.nodes;\n\n // Verify that all requested entities exist by name!\n const responses = new Map();\n for (let name of order) {\n const provider = entities.get(name);\n if (!provider) {\n throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);\n }\n\n // Each promise should only be triggered when the things it depends on have been resolved\n const depends_on = dag.get(name) || [];\n const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));\n\n const this_result = prereq_promises.then((prior_results) => {\n // Each request will be told the name of the provider that requested it. This can be used during post-processing,\n // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)\n // This has a secondary effect: it ensures that any changes made to \"shared\" options in one adapter will\n // not leak out to others via a mutable shared object reference.\n const options = Object.assign({_provider_name: name}, shared_options);\n return provider.getData(options, ...prior_results);\n });\n responses.set(name, this_result);\n }\n return Promise.all([...responses.values()])\n .then((all_results) => {\n if (consolidate) {\n // Some usages- eg fetch + data join tasks- will only require the last response in the sequence\n // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)\n return all_results[all_results.length - 1];\n }\n return all_results;\n });\n}\n\n\nexport {getLinkedData};\n\n// For testing only\nexport {_parse_declaration};\n","/**\n * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.\n */\nimport { clone } from './util';\n\n\n/**\n * Simple grouping function, used to identify sets of records for joining.\n *\n * Used internally by join helpers, exported mainly for unit testing\n * @memberOf module:undercomplicate\n * @param {object[]} records\n * @param {string} group_key\n * @returns {Map}\n */\nfunction groupBy(records, group_key) {\n const result = new Map();\n for (let item of records) {\n const item_group = item[group_key];\n\n if (typeof item_group === 'undefined') {\n throw new Error(`All records must specify a value for the field \"${group_key}\"`);\n }\n if (typeof item_group === 'object') {\n // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)\n throw new Error('Attempted to group on a field with non-primitive values');\n }\n\n let group = result.get(item_group);\n if (!group) {\n group = [];\n result.set(item_group, group);\n }\n group.push(item);\n }\n return result;\n}\n\n\nfunction _any_match(type, left, right, left_key, right_key) {\n // Helper that consolidates logic for all three join types\n const right_index = groupBy(right, right_key);\n const results = [];\n for (let item of left) {\n const left_match_value = item[left_key];\n const right_matches = right_index.get(left_match_value) || [];\n if (right_matches.length) {\n // Record appears on both left and right; equiv to an inner join\n results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));\n } else if (type !== 'inner') {\n // Record appears on left but not right\n results.push(clone(item));\n }\n }\n\n if (type === 'outer') {\n // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side\n const left_index = groupBy(left, left_key);\n for (let item of right) {\n const right_match_value = item[right_key];\n const left_matches = left_index.get(right_match_value) || [];\n if (!left_matches.length) {\n results.push(clone(item));\n }\n }\n }\n return results;\n}\n\n/**\n * Equivalent to LEFT OUTER JOIN in SQL. Return all left records, joined to matching right data where appropriate.\n * @memberOf module:undercomplicate\n * @param {Object[]} left The left side recordset\n * @param {Object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction left_match(left, right, left_key, right_key) {\n return _any_match('left', ...arguments);\n}\n\n/**\n * Equivalent to INNER JOIN in SQL. Only return record joins if the key field has a match on both left and right.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction inner_match(left, right, left_key, right_key) {\n return _any_match('inner', ...arguments);\n}\n\n/**\n * Equivalent to FULL OUTER JOIN in SQL. Return records in either recordset, joined where appropriate.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction full_outer_match(left, right, left_key, right_key) {\n return _any_match('outer', ...arguments);\n}\n\nexport {left_match, inner_match, full_outer_match, groupBy};\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 1 }]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 2 }]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nimport {BaseUrlAdapter} from './undercomplicate';\n\nimport {parseMarker} from '../helpers/parse';\n\n/**\n * Replaced with the BaseLZAdapter class.\n * @public\n * @deprecated\n */\nclass BaseAdapter {\n constructor() {\n throw new Error('The \"BaseAdapter\" and \"BaseApiAdapter\" classes have been replaced in LocusZoom 0.14. See migration guide for details.');\n }\n}\n\n/**\n * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @deprecated\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {}\n\n\n/**\n * @extends module:undercomplicate.BaseUrlAdapter\n * @inheritDoc\n */\nclass BaseLZAdapter extends BaseUrlAdapter {\n /**\n * @param [config.cache_enabled=true]\n * @param [config.cache_size=3]\n * @param [config.url]\n * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name.\n * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant)\n * Typically, this is only disabled if the response payload is very unusual\n * @param {String[]} [config.limit_fields=null] If an API returns far more data than is needed, this can be used to simplify\n * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.\n */\n constructor(config = {}) {\n if (config.params) {\n // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places.\n console.warn('Deprecation warning: all options in \"config.params\" should now be specified as top level keys.');\n Object.assign(config, config.params || {});\n delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality!\n }\n super(config);\n\n // Prefix the namespace for this source to all fieldnames: id -> assoc.id\n // This is useful for almost all layers because the layout object says where to find every field, exactly.\n // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on\n // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear\n // in the response. (gene_name instead of genes.gene_name)\n const { prefix_namespace = true, limit_fields } = config;\n this._prefix_namespace = prefix_namespace;\n this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields\n }\n\n /**\n * Determine how a particular request will be identified in cache. Most LZ requests are region based,\n * so the default is a string concatenation of `chr_start_end`. This adapter is \"region aware\"- if the user\n * zooms in, it won't trigger a network request because we alread have the data needed.\n * @param options Receives plot.state plus any other request options defined by this source\n * @returns {string}\n * @public\n */\n _getCacheKey(options) {\n // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default\n let {chr, start, end} = options; // Current view: plot.state\n\n // Does a prior cache hit qualify as a superset of the ROI?\n const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end);\n if (superset) {\n ({ chr, start, end } = superset.metadata);\n }\n\n // The default cache key is region-based, and this method only returns the region-based part of the cache hit\n // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check\n options._cache_meta = { chr, start, end };\n return `${chr}_${start}_${end}`;\n }\n\n /**\n * Add the \"local namespace\" as a prefix for every field returned for this request. Eg if the association api\n * returns a field called variant, and the source is referred to as \"assoc\" within a particular data layer, then\n * the returned records will have a field called \"assoc:variant\"\n *\n * @param records\n * @param options\n * @returns {*}\n * @public\n */\n _postProcessResponse(records, options) {\n if (!this._prefix_namespace || !Array.isArray(records)) {\n return records;\n }\n\n // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for\n // assoc data might see \"variant\" as \"assoc.variant\"\n const { _limit_fields } = this;\n const { _provider_name } = options;\n\n return records.map((row) => {\n return Object.entries(row).reduce(\n (acc, [label, value]) => {\n // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload.\n if (!_limit_fields || _limit_fields.has(label)) {\n acc[`${_provider_name}:${label}`] = value;\n }\n return acc;\n },\n {},\n );\n });\n }\n\n /**\n * Convenience method, manually called in LZ sources that deal with dependent data.\n *\n * In the last step of fetching data, LZ adds a prefix to each field name.\n * This means that operations like \"build query based on prior data\" can't just ask for \"log_pvalue\" because\n * they are receiving \"assoc:log_pvalue\" or some such unknown prefix.\n *\n * This helper lets us use dependent data more easily. Not every adapter needs to use this method.\n *\n * @param {Object} a_record One record (often the first one in a set of records)\n * @param {String} fieldname The desired fieldname, eg \"log_pvalue\"\n */\n _findPrefixedKey(a_record, fieldname) {\n const suffixer = new RegExp(`:${fieldname}$`);\n const match = Object.keys(a_record).find((key) => suffixer.test(key));\n if (!match) {\n throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`);\n }\n return match;\n }\n}\n\n\n/**\n * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies\n * of one particular web server.\n * @extends module:LocusZoom_Adapters~BaseLZAdapter\n * @inheritDoc\n */\nclass BaseUMAdapter extends BaseLZAdapter {\n /**\n * @param {Object} config\n * @param {String} [config.build] The genome build to be used by all requests for this adapter. (UMich APIs are all genome build aware). \"GRCh37\" or \"GRCh38\"\n */\n constructor(config = {}) {\n super(config);\n // The UM portaldev API accepts an (optional) parameter \"genome_build\"\n this._genome_build = config.genome_build || config.build;\n }\n\n _validateBuildSource(build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${this.constructor.name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`);\n }\n }\n\n // Special behavior for the UM portaldev API: col -> row format normalization\n /**\n * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.\n * @param response_text\n * @param options\n * @returns {Object[]}\n * @public\n */\n _normalizeResponse(response_text, options) {\n let data = super._normalizeResponse(...arguments);\n // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload\n data = data.data || data;\n\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the columns, and create an object for each row record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n}\n\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @inheritDoc\n *\n * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL\n */\nclass AssociationLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files\n const { source } = config;\n this._source_id = source;\n }\n\n _getURL (request_options) {\n const {chr, start, end} = request_options;\n const base = super._getURL(request_options);\n return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`;\n }\n}\n\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GwasCatalogLZ extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build] The genome build to use when requesting the specific genomic region.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n const source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given.\n // We will avoid transforming or modifying the payload.\n this._prefix_namespace = false;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GeneConstraintLZ extends BaseLZAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config = {}) {\n super(config);\n this._prefix_namespace = false;\n }\n\n _buildRequestOptions(state, genes_data) {\n const build = state.genome_build || this._config.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = new Set();\n for (let gene of genes_data) {\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n unique_gene_names.add(gene.gene_name);\n }\n\n state.query = [...unique_gene_names.values()].map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n state.build = build;\n return Object.assign({}, state);\n }\n\n _performRequest(options) {\n let {query, build} = options;\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears to set max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve([]);\n }\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n\n const url = this._getURL(options);\n\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.\n */\n _normalizeResponse(response_text) {\n if (typeof response_text !== 'string') {\n // If the query short-circuits, we receive an empty list instead of a string\n return response_text;\n }\n const data = JSON.parse(response_text);\n return data.data;\n }\n}\n\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant and yse that as the LD reference variant.\n *\n * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly\n * if this information is not provided.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass LDServer extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.method='rsquare'] The metric used to calculate LD\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n __find_ld_refvar(state, assoc_data) {\n const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant');\n const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue');\n\n // Determine the reference variant (via user selected OR automatic-per-track)\n let refvar;\n let best_hit = {};\n if (state.ldrefvar) {\n // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data\n refvar = state.ldrefvar;\n best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {};\n } else {\n // find highest log-value and associated var spec\n let best_logp = 0;\n for (let item of assoc_data) {\n const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item;\n if (log_pvalue > best_logp) {\n best_logp = log_pvalue;\n refvar = variant;\n best_hit = item;\n }\n }\n }\n\n // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting.\n // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase.\n best_hit.lz_is_ld_refvar = true;\n\n // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server,\n // the variant fields must be normalized to a specific format. All later LD operations will use that format.\n const match = parseMarker(refvar, true);\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n\n const [chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip?\n if (ref && alt) {\n refvar += `_${ref}/${alt}`;\n }\n\n const coord = +pos;\n // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably\n // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby.\n if ((coord && state.ldrefvar && state.chr) && (chrom !== String(state.chr) || coord < state.start || coord > state.end)) {\n // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a\n // *copy* of plot.state, so wiping here doesn't remove the original value.\n state.ldrefvar = null;\n return this.__find_ld_refvar(state, assoc_data);\n }\n\n // Return the reference variant, in a normalized format suitable for LDServer queries\n return refvar;\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n // No variants, so no need to annotate association data with LD!\n // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region)\n base._skip_request = true;\n return base;\n }\n\n base.ld_refvar = this.__find_ld_refvar(state, assoc_data);\n\n // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config\n const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let ld_source = state.ld_source || this._config.source || '1000G';\n const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL\n\n if (ld_source === '1000G' && genome_build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n ld_source = '1000G-FRZ09';\n }\n\n this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option\n return Object.assign({}, base, { genome_build, ld_source, ld_population });\n }\n\n _getURL(request_options) {\n const method = this._config.method || 'rsquare';\n const {\n chr, start, end,\n ld_refvar,\n genome_build, ld_source, ld_population,\n } = request_options;\n\n const base = super._getURL(request_options);\n\n return [\n base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(ld_refvar),\n '&chrom=', encodeURIComponent(chr),\n '&start=', encodeURIComponent(start),\n '&stop=', encodeURIComponent(end),\n ].join('');\n }\n\n _getCacheKey(options) {\n // LD is keyed by more than just region; append other parameters to the base cache key\n const base = super._getCacheKey(options);\n const { ld_refvar, ld_source, ld_population } = options;\n return `${base}_${ld_refvar}_${ld_source}_${ld_population}`;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird.\n return Promise.resolve([]);\n }\n\n const url = this._getURL(options);\n\n // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseUMAdapter {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['position', 'recomb_rate'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {object} config.data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseLZAdapter {\n constructor(config = {}) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n super(...arguments);\n const { data } = config;\n if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key\n throw new Error(\"'StaticSource' must provide data as required option 'config.data'\");\n }\n this._data = data;\n }\n\n _performRequest(options) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will\n * be used to provide PheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const base = super._getURL(request_options);\n const url = [\n base,\n \"?filter=variant eq '\", encodeURIComponent(request_options.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n _getCacheKey(options) {\n // Not a region based source; don't do anything smart for cache check\n return this._getURL(options);\n }\n}\n\n// Deprecated symbols\nexport { BaseAdapter, BaseApiAdapter };\n\n// Usually used as a parent class for custom code\nexport { BaseLZAdapter, BaseUMAdapter };\n\n// Usually used as a standalone class\nexport {\n AssociationLZ,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","import {LRUCache} from './lru_cache';\nimport {clone} from './util';\n\n/**\n * @param {boolean} [config.cache_enabled=true] Whether to enable the LRU cache, and store a copy of the normalized and parsed response data.\n * Turned on by default for most remote requests; turn off if you are using another datastore (like Vuex) or if the response body uses too much memory.\n * @param {number} [config.cache_size=3] How many requests to cache. Track-dependent annotations like LD might benefit\n * from caching more items, while very large payloads (like, um, TOPMED LD) might benefit from a smaller cache size.\n * For most LocusZoom usages, the cache is \"region aware\": zooming in will use cached data, not a separate request\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseAdapter {\n constructor(config = {}) {\n this._config = config;\n const {\n // Cache control\n cache_enabled = true,\n cache_size = 3,\n } = config;\n this._enable_cache = cache_enabled;\n this._cache = new LRUCache(cache_size);\n }\n\n /**\n * Build an object with options that control the request. This can take into account both explicit options, and prior data.\n * @param {Object} options Any global options passed in via `getData`. Eg, in locuszoom, every request is passed a copy of `plot.state` as the options object, in which case every adapter would expect certain basic information like `chr, start, end` to be available.\n * @param {Object[]} dependent_data If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, `ld(assoc)` means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.\n * @returns {*} An options object containing initial options, plus any calculated values relevant to the request.\n * @public\n */\n _buildRequestOptions(options, dependent_data) {\n // Perform any pre-processing required that may influence the request. Receives an array with the payloads\n // for each request that preceded this one in the dependency chain\n // This method may optionally take dependent data into account. For many simple adapters, there won't be any dependent data!\n return Object.assign({}, options);\n }\n\n /**\n * Determine how this request is uniquely identified in cache. Usually this is an exact match for the same key, but it doesn't have to be.\n * The LRU cache implements a `find` method, which means that a cache item can optionally be identified by its node\n * `metadata` (instead of exact key match).\n * This is useful for situations where the user zooms in to a smaller region and wants the original request to\n * count as a cache hit. See subclasses for example.\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {*} This is often a string concatenating unique values for a compound cache key, like `chr_start_end`. If null, it is treated as a cache miss.\n * @public\n */\n _getCacheKey(options) {\n /* istanbul ignore next */\n if (this._enable_cache) {\n throw new Error('Method not implemented');\n }\n return null;\n }\n\n /**\n * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {Promise}\n * @public\n */\n _performRequest(options) {\n /* istanbul ignore next */\n throw new Error('Not implemented');\n }\n\n /**\n * Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.\n * @param {*} response_text The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON.\n * @param {Object} options Request options. These are not typically used when normalizing a response, but the object is available.\n * @returns {*} A list of objects, each object representing one row of data `{column_name: value_for_row}`\n * @public\n */\n _normalizeResponse(response_text, options) {\n return response_text;\n }\n\n /**\n * Perform custom client-side operations on the retrieved data. For example, add calculated fields or\n * perform rapid client-side filtering on cached data. Annotations are applied after cache, which means\n * that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.\n *\n * This result is currently not cached, but it may become so in the future as responsibility for dynamic UI\n * behavior moves to other layers of the application.\n * @param {Object[]} records\n * @param {Object} options\n * @returns {*}\n * @public\n */\n _annotateRecords(records, options) {\n return records;\n }\n\n /**\n * A hook to transform the response after all operations are done. For example, this can be used to prefix fields\n * with a namespace unique to the request, like `log_pvalue` -> `assoc:log_pvalue`. (by applying namespace prefixes to field names last,\n * annotations and validation can happen on the actual API payload, without having to guess what the fields were renamed to).\n * @param records\n * @param options\n * @public\n */\n _postProcessResponse(records, options) {\n return records;\n }\n\n /**\n * All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.\n * @param {object} options Shared options for this request. In LocusZoom, this is typically a copy of `plot.state`.\n * @param {Array[]} dependent_data Zero or more recordsets corresponding to each individual adapter that this one depends on.\n * Can be used to build a request that takes into account prior data.\n * @returns {Promise<*>}\n */\n getData(options = {}, ...dependent_data) {\n // Public facing method to define, perform, and process the request\n options = this._buildRequestOptions(options, ...dependent_data);\n\n const cache_key = this._getCacheKey(options);\n\n // Then retrieval and parse steps: parse + normalize response, annotate\n let result;\n if (this._enable_cache && this._cache.has(cache_key)) {\n result = this._cache.get(cache_key);\n } else {\n // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)\n // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry\n // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,\n // even if the actual cache key wasn't an exact match. (see subclasses for an example; this class is generic)\n result = Promise.resolve(this._performRequest(options))\n // Note: we cache the normalized (parsed) response\n .then((text) => this._normalizeResponse(text, options));\n this._cache.add(cache_key, result, options._cache_meta);\n // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request\n // Otherwise, temporary failures couldn't be resolved by trying again in a moment\n // TODO: In the future, consider providing a way to skip requests (eg, a sentinel value to flag something\n // as not cacheable, like \"no dependent data means no request... but maybe in another place this is used, there will be different dependent data and a request would make sense\")\n result.catch((e) => this._cache.remove(cache_key));\n }\n\n return result\n // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache\n .then((data) => clone(data))\n .then((records) => this._annotateRecords(records, options))\n .then((records) => this._postProcessResponse(records, options));\n }\n}\n\n\n/**\n * Fetch data over the web, usually from a REST API that returns JSON\n * @param {string} config.url The URL to request\n * @extends module:undercomplicate.BaseAdapter\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseUrlAdapter extends BaseAdapter {\n constructor(config = {}) {\n super(config);\n this._url = config.url;\n }\n\n\n /**\n * Default cache key is the URL for this request\n * @public\n */\n _getCacheKey(options) {\n return this._getURL(options);\n }\n\n /**\n * In many cases, the base url should be modified with query parameters based on request options.\n * @param options\n * @returns {*}\n * @private\n */\n _getURL(options) {\n return this._url;\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.\n // (not validating in constructor allows URL adapter to be used as more generic parent class)\n if (!this._url) {\n throw new Error('Web based resources must specify a resource URL as option \"url\"');\n }\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n });\n }\n\n _normalizeResponse(response_text, options) {\n if (typeof response_text === 'string') {\n return JSON.parse(response_text);\n }\n // Some custom usages will return other datatypes. These would need to be handled by custom normalization logic in a subclass.\n return response_text;\n }\n}\n\nexport { BaseAdapter, BaseUrlAdapter };\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value,\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import TRANSFORMS from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n // Two scenarios: we are requesting a field by full name, OR there are transforms to apply\n // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`\n const field_pattern = /^(?:\\w+:\\w+|^\\w+)(?:\\|\\w+)*$/;\n if (!field_pattern.test(field)) {\n throw new Error(`Invalid field specifier: '${field}'`);\n }\n\n const [name, ...transforms] = field.split('|');\n\n this.full_name = field; // fieldname + transforms\n this.field_name = name; // just fieldname\n this.transformations = transforms.map((name) => TRANSFORMS.get(name));\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (data[this.field_name] !== undefined) { // Fallback: value sans transforms\n val = data[this.field_name];\n } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations\n val = extra[this.field_name];\n } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {},\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable,\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * @module\n * @private\n */\nimport {getLinkedData} from './undercomplicate';\n\nimport { DATA_OPS } from '../registry';\n\n\nclass DataOperation {\n /**\n * Perform a data operation (such as a join)\n * @param {String} join_type\n * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!\n * @param params Optional user/layout parameters to be passed to the data function\n */\n constructor(join_type, initiator, params) {\n this._callable = DATA_OPS.get(join_type);\n this._initiator = initiator;\n this._params = params || [];\n }\n\n getData(plot_state, ...dependent_recordsets) {\n // Most operations are joins: they receive two pieces of data (eg left + right)\n // Other ops are possible, like consolidating just one set of records to best value per key\n // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]\n\n // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options\n const context = {plot_state, data_layer: this._initiator};\n return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));\n }\n}\n\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a\n * designated order.\n *\n * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can\n * perform its own calculations, filter results, and apply transforms without influencing other layers.\n * (while still respecting a shared cache where appropriate)\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n /**\n * Parse the data layer configuration when a layer is first created.\n * Validate config, and return entities and dependencies in a format usable for data retrieval.\n * This is used by data layers, and also other data-retrieval functions (like subscribeToDate).\n *\n * Inherent assumptions:\n * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed.\n * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be\n * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the\n * removed adapter.\n * @param {Object} namespace_options\n * @param {Array} data_operations\n * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations,\n * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate\n * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care!\n * @returns {Array} Map of entities and list of dependencies\n */\n config_to_sources(namespace_options = {}, data_operations = [], initiator) {\n const entities = new Map();\n const namespace_local_names = Object.keys(namespace_options);\n\n // 1. Specify how to coordinate data. Precedence:\n // a) EXPLICIT fetch logic,\n // b) IMPLICIT auto-generate fetch order if there is only one NS,\n // c) Throw \"spec required\" error if > 1, because 2 adapters may need to be fetched in a sequence\n let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from}\n if (!dependency_order) {\n dependency_order = { type: 'fetch', from: namespace_local_names };\n data_operations.unshift(dependency_order);\n }\n\n // Validate that all NS items are available to the root requester in DataSources. All layers recognize a\n // default value, eg people copying the examples tend to have defined a datasource called \"assoc\"\n const ns_pattern = /^\\w+$/;\n for (let [local_name, global_name] of Object.entries(namespace_options)) {\n if (!ns_pattern.test(local_name)) {\n throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`);\n }\n\n const source = this._sources.get(global_name);\n if (!source) {\n throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`);\n }\n entities.set(local_name, source);\n\n // Note: Dependency spec checker will consider \"ld(assoc)\" to match a namespace called \"ld\"\n if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) {\n // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join.\n // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch\n // Thus the default behavior is \"fetch all namespaces as though they don't depend on anything.\n // If they depend on something, only then does \"data_ops[@type=fetch].from\" need to be mutated\n dependency_order.from.push(local_name);\n }\n }\n\n let dependencies = Array.from(dependency_order.from);\n\n // Now check all joins. Are namespaces valid? Are they requesting known data?\n for (let config of data_operations) {\n let {type, name, requires, params} = config;\n if (type !== 'fetch') {\n let namecount = 0;\n if (!name) {\n name = config.name = `join${namecount}`;\n namecount += 1;\n }\n\n if (entities.has(name)) {\n throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`);\n }\n requires.forEach((require_name) => {\n if (!entities.has(require_name)) {\n throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`);\n }\n });\n\n const task = new DataOperation(type, initiator, params);\n entities.set(name, task);\n dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB)\n }\n }\n return [entities, dependencies];\n }\n\n /**\n * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end)\n * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts.\n * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances\n * (things that implement a method getData).\n * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order\n * @returns {Promise}\n */\n getData(plot_state, entities, dependencies) {\n if (!dependencies.length) {\n return Promise.resolve([]);\n }\n // The last dependency (usually the last join operation) determines the last thing returned.\n return getLinkedData(plot_state, entities, dependencies, true);\n }\n}\n\n\nexport default Requester;\n\nexport {DataOperation as _JoinTask};\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay,\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import WIDGETS from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n const options = this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n this.addWidget(layout);\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown)\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Add a new widget to the toolbar.\n * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to:\n * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here.\n * - call widget.show() to ensure that the widget is initialized and rendered correctly\n * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default.\n * @param {Object} layout The layout object describing the desired widget\n * @returns {layout.type}\n */\n addWidget(layout) {\n try {\n const widget = WIDGETS.create(layout.type, layout, this);\n this.widgets.push(widget);\n return widget;\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 14,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n const layer_legend = this.parent.data_layers[id].layout.legend;\n if (Array.isArray(layer_legend)) {\n layer_legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape === 'ribbon') {\n // Color ribbons describe a series of color stops: small boxes of color across a continuous\n // scale. Drawn horizontally, or vertically, like:\n // [red | orange | yellow | green ] label\n // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way.\n const width = +element.width || 25;\n const height = +element.height || width;\n const is_horizontal = (element.orientation || 'vertical') === 'horizontal';\n let color_stops = element.color_stops;\n\n const all_elements = selector.append('g');\n const ribbon_group = all_elements.append('g');\n const axis_group = all_elements.append('g');\n let axis_offset = 0;\n if (element.tick_labels) {\n let range;\n if (is_horizontal) {\n range = [0, width * color_stops.length - 1]; // 1 px offset to align tick with inner borders\n } else {\n range = [height * color_stops.length - 1, 0];\n }\n const scale = d3.scaleLinear()\n .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode\n .range(range);\n const axis = (is_horizontal ? d3.axisTop : d3.axisRight)(scale)\n .tickSize(3)\n .tickValues(element.tick_labels)\n .tickFormat((v) => v);\n axis_group\n .call(axis)\n .attr('class', 'lz-axis');\n let bcr = axis_group.node().getBoundingClientRect();\n axis_offset = bcr.height;\n }\n if (is_horizontal) {\n // Shift axis down (so that tick marks aren't above the origin)\n axis_group\n .attr('transform', `translate(0, ${axis_offset})`);\n // Ribbon appears below axis\n ribbon_group\n .attr('transform', `translate(0, ${axis_offset})`);\n } else {\n // Vertical mode: Shift axis ticks to the right of the ribbon\n all_elements.attr('transform', 'translate(5, 0)');\n axis_group\n .attr('transform', `translate(${width}, 0)`);\n }\n\n if (!is_horizontal) {\n // Vertical mode: renders top -> bottom but scale is usually specified low..high\n color_stops = color_stops.slice();\n color_stops.reverse();\n }\n for (let i = 0; i < color_stops.length; i++) {\n const color = color_stops[i];\n const to_next_marking = is_horizontal ? `translate(${width * i}, 0)` : `translate(0, ${height * i})`;\n ribbon_group\n .append('rect')\n .attr('class', element.class || '')\n .attr('stroke', 'black')\n .attr('transform', to_next_marking)\n .attr('stroke-width', 0.5)\n .attr('width', width)\n .attr('height', height)\n .attr('fill', color)\n .call(applyStyles, element.style || {});\n }\n\n // Note: In vertical mode, it's usually easier to put the label above the legend as a separate marker\n // This is because the legend element label is drawn last (can't use it's size to position the ribbon, which is drawn first)\n if (!is_horizontal && element.label) {\n throw new Error('Legend labels not supported for vertical ribbons (use a separate legend item as text instead)');\n }\n // This only makes sense for horizontal labels.\n label_x = (width * color_stops.length + padding);\n label_y += axis_offset;\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset] The distance between the axis title and the axis. Use this to prevent\n * the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly.\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n if (typeof layout.id !== 'string' || !layout.id) {\n throw new Error('Panel layouts must specify \"id\"');\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this._layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this._state_id = this.id;\n this.state[this._state_id] = this.state[this._state_id] || {};\n } else {\n this.state = null;\n this._state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this._data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this._data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this._zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this._event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this._event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this._data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this._data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id]._layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n const target_layer = this.data_layers[id];\n if (!target_layer) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n target_layer.destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (target_layer.svg.container) {\n target_layer.svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(target_layer._layout_idx, 1);\n delete this.state[target_layer._state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id]._layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n const { cliparea } = this.layout;\n\n // Set and position the inner border, style if necessary\n const { margin } = this.layout;\n this.inner_border\n .attr('x', margin.left)\n .attr('y', margin.top)\n .attr('width', this.parent_plot.layout.width - (margin.left + margin.right))\n .attr('height', this.layout.height - (margin.top + margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n const axes_config = this.layout.axes;\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (axes_config.x.range) {\n base_x_range.start = axes_config.x.range.start || base_x_range.start;\n base_x_range.end = axes_config.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: cliparea.height, end: 0 };\n if (axes_config.y1.range) {\n base_y1_range.start = axes_config.y1.range.start || base_y1_range.start;\n base_y1_range.end = axes_config.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: cliparea.height, end: 0 };\n if (axes_config.y2.range) {\n base_y2_range.start = axes_config.y2.range.start || base_y2_range.start;\n base_y2_range.end = axes_config.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n let { _interaction } = this.parent;\n const current_drag = _interaction.dragging;\n if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (_interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = _interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = _interaction.zooming.center - margin.left - this.layout.origin.x;\n const offset_ratio = anchor / cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (current_drag) {\n switch (current_drag.method) {\n case 'background':\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n } else {\n anchor = current_drag.start_x - margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${current_drag.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y;\n ranges[y_shifted][1] = +current_drag.dragged_y;\n } else {\n anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3);\n ranges[y_shifted][0] = cliparea.height;\n ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent._interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n // Redefine b/c might have been changed during call to parent re-render\n _interaction = this.parent._interaction;\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this._zoom_timeout !== null) {\n clearTimeout(this._zoom_timeout);\n }\n this._zoom_timeout = setTimeout(() => {\n this.parent._interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this._initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((id) => {\n const axis = this.layout.axes[id];\n if (!Object.keys(axis).length || axis.render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n axis.render = false;\n } else {\n axis.render = true;\n axis.label = axis.label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n const layout = this.layout;\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n layout.height = Math.max(Math.round(+height), layout.min_height);\n }\n }\n layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0);\n layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', layout.height);\n }\n if (this._initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n const { cliparea, margin } = this.layout;\n if (!isNaN(top) && top >= 0) {\n margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (margin.top + margin.bottom > this.layout.height) {\n extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2);\n margin.top -= extra;\n margin.bottom -= extra;\n }\n if (margin.left + margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2);\n margin.left -= extra;\n margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n margin[m] = Math.max(margin[m], 0);\n });\n cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0);\n cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0);\n cliparea.origin.x = margin.left;\n cliparea.origin.y = margin.top;\n\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this._data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent._panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n const { parent } = this;\n const y_index = this.layout.y_index;\n if (parent._panel_ids_by_y_index[y_index - 1]) {\n parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1];\n parent._panel_ids_by_y_index[y_index - 1] = this.id;\n parent.applyPanelYIndexesToPanelLayouts();\n parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n const { _panel_ids_by_y_index } = this.parent;\n if (_panel_ids_by_y_index[this.layout.y_index + 1]) {\n _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1];\n _panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this._data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this._data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this._data_promises)\n .then(() => {\n this._initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([\\w+_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future.\n * @event data_from_layer\n * @property {object} data\n * @property {String} data.layer The fully qualified ID of the layer emitting this event\n * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum\n * element. It reflects all namespaces and data operations used by that layer.\n * @see event:any_lz_event\n */\n\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this._initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this._panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this._remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this._interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this._event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this._event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this._panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this._panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id]._layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer._layer_state;\n delete this.layout.state[layer._state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n const target_panel = this.panels[id];\n if (!target_panel) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this._panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n target_panel.loader.hide();\n target_panel.toolbar.destroy(true);\n target_panel.curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (target_panel.svg.container) {\n target_panel.svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(target_panel._layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id]._layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the\n * structure of the data, instead of just displaying something.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @listens event:data_from_layer\n * @param {Object} [opts] Options\n * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched.\n * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details.\n * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested,\n * this is usually required in order to specify dependency order and join operations. See data layer documentation for details.\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(opts, success_callback) {\n const { from_layer, namespace, data_operations, onerror } = opts;\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = onerror || function (err) {\n console.error('An error occurred while acting on an external callback', err);\n };\n\n if (from_layer) {\n // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places.\n const base_prefix = `${this.getBaseId()}.`;\n // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code.\n const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`;\n\n // Ensure that a valid layer exists to watch\n let is_valid_layer = false;\n for (let p of Object.values(this.panels)) {\n is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target);\n if (is_valid_layer) {\n break;\n }\n }\n if (!is_valid_layer) {\n throw new Error(`Could not subscribe to unknown data layer ${layer_target}`);\n }\n\n const listener = (eventData) => {\n if (eventData.data.layer !== layer_target) {\n // Same event name fires for many layers; only fire success cb for the one layer we want\n return;\n }\n try {\n success_callback(eventData.data.content, this);\n } catch (error) {\n error_callback(error);\n }\n };\n\n this.on('data_from_layer', listener);\n return listener;\n }\n\n // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of\n // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized\n // in connection to some other visualization)\n if (!namespace) {\n throw new Error(\"subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option\");\n }\n\n const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot.\n const listener = () => {\n try {\n // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely,\n // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame.\n this.lzd.getData(this.state, entities, dependencies)\n .then((new_data) => success_callback(new_data, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this._remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this._remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this._remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this._initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /**\n * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another,\n * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties,\n * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.\n * @public\n */\n mutateLayout() {\n Object.values(this.panels).forEach((panel) => {\n Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout());\n });\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n const { _interaction } = this;\n if (panel_id) {\n return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(_interaction.dragging || _interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this._panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this._initialized) {\n this._panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let panel of Object.values(this.panels)) {\n if (panel.layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_layout = panel.layout;\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel_layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel_layout.margin.right, 0);\n panel_layout.width += delta;\n panel_layout.margin.left = x_linked_margins.left;\n panel_layout.margin.right = x_linked_margins.right;\n panel_layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height,\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n const resize_listener = () => this.rescaleSVG();\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener\n // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed\n if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers\n const options = { root: document.documentElement, threshold: 0.9 };\n const observer = new IntersectionObserver((entries, observer) => {\n if (entries.some((entry) => entry.intersectionRatio > 0)) {\n this.rescaleSVG();\n }\n }, options);\n // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup\n observer.observe(this.container);\n }\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this._mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this._panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent._panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent._panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this._panel_boundaries.hide_timeout);\n this._panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this._panel_boundaries.hide_timeout = setTimeout(() => {\n this._panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this._mouse_guide.vertical.attr('x', -1);\n this._mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this._mouse_guide.vertical.attr('x', coords[0]);\n this._mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n const { _interaction } = this;\n if (_interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x;\n _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y;\n this.panels[_interaction.panel_id].render();\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this._initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this._interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n const { _interaction } = this;\n if (!_interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[_interaction.panel_id] != 'object') {\n this._interaction = {};\n return this;\n }\n const panel = this.panels[_interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel._data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (_interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (_interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (_interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(_interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this._interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} value value\n */\nconst if_value = (parameters, value) => {\n if (typeof value == 'undefined' || parameters.field_value !== value) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} value value\n * @returns {*}\n */\nconst numerical_bin = (parameters, value) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+value < prev || (+value >= prev && +value < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @function ordinal_cycle\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n *\n * @function stable_choice\n *\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, value) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return nullval;\n }\n if (+value <= parameters.breaks[0]) {\n return values[0];\n } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +value && breaks[idx] >= +value) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\n/**\n * Calculate the effect direction based on beta, or the combination of beta and standard error.\n * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.\n *\n * @function effect_direction\n * @param parameters\n * @param parameters.'+' The value to return if the effect direction is positive\n * @param parameters.'-' The value to return if the effect direction is positive\n * @param parameters.beta_field The name of the field containing beta\n * @param parameters.stderr_beta_field The name of the field containing stderr_beta\n * @param {Object} input This function should receive the entire datum object, rather than one single field\n * @returns {null}\n */\nfunction effect_direction(parameters, input) {\n if (input === undefined) {\n return null;\n }\n\n const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters;\n\n if (!beta_field || !stderr_beta_field) {\n throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`);\n }\n\n const beta_val = input[beta_field];\n const se_val = input[stderr_beta_field];\n\n if (beta_val !== undefined) {\n if (se_val !== undefined) {\n if ((beta_val - 1.96 * se_val) > 0) {\n return plus_result;\n } else if ((beta_val + 1.96 * se_val) < 0) {\n return neg_result || null;\n }\n } else {\n if (beta_val > 0) {\n return plus_result;\n } else if (beta_val < 0) {\n return neg_result;\n }\n }\n }\n // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid\n // about expected data formats for layouts.\n return null;\n}\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, findFields, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n/**\n * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.\n * @property {module:LocusZoom_DataFunctions|'fetch'} type\n * @property {String[]} [from] For operations of type \"fetch\", this is required. By default, it will fill in any items provided in \"namespace\" (everything specified in namespace triggers an adapter/network request)\n * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace).\n * Eg, for ld to be fetched after association data, specify \"ld(assoc)\". Most LocusZoom examples fill in all items, in order to make the examples more clear.\n * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation.\n * Eg, if the retrieved data is combined via several left joins in series: `name: \"assoc_plus_ld\"` -> feeds into `{name: \"final\", requires: [\"assoc_plus_ld\"]}`\n * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just\n * the names of other namespaces (or data operations) whose results must be available before a join can be performed\n * @property {String[]} params Any user-defined parameters that should be passed to the particular join function\n * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']`\n * Separate from this section, data functions will also receive a copy of \"plot.state\" automatically.\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or \"line\" for a path.\n * A special \"ribbon\" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD)\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {'vertical'|'horizontal'} [orientation='vertical'] For shape \"ribbon\", specifies whether to draw the ribbon vertically or horizontally.\n * @property {Array} [tick_labels] For shape \"ribbon\", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops.\n * @property {String[]} [color_stops] For shape \"ribbon\", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels.\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n namespace: {},\n data_operations: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is\n * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely\n * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is\n * your job to assure that all of the expected fields are present in every element)\n * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data (\"assoc\")\n * to the globally unique name for something defined in LocusZoom.DataSources.\n * Namespaces allow a single layout to be reused to plot many tracks of the same type: \"given some form of association data, plot it\".\n * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse\n * a layout with a new provider of data- like plotting two association studies stacked together-\n * only the namespace section of the layout needs to be overridden.\n * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})`\n * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions})\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this._layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this._state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this._layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this._tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this._global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n\n // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval\n this._data_contract = new Set(); // List of all fields requested by the layout\n this._entities = new Map();\n this._dependencies = [];\n this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout\n }\n\n /****** Public interface: methods for manipulating the layer from other parts of LZ */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index + 1]) {\n layer_order[current_index] = layer_order[current_index + 1];\n layer_order[current_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index - 1]) {\n layer_order[current_index] = layer_order[current_index - 1];\n layer_order[current_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this._layer_state.extra_fields[id]) {\n this._layer_state.extra_fields[id] = {};\n }\n this._layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /**\n * A list of operations that should be run when the layout is mutated\n * Typically, these are things done once when a layout is first specified, that would not automatically\n * update when the layout was changed.\n * @public\n */\n mutateLayout() {\n // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract.\n if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester\n const { namespace, data_operations } = this.layout;\n this._data_contract = findFields(this.layout, Object.keys(namespace));\n const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this);\n this._entities = entities;\n this._dependencies = dependencies;\n }\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n *\n * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that\n * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data),\n * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}`\n * @protected\n * @param {Object} element The data associated with a particular element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression\n const id_field = this.layout.id_field;\n let value = element[id_field];\n if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) {\n // No field value was found directly, but if it looks like a template expression, next, try parsing that\n // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot!\n value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient\n }\n if (value === null || value === undefined) {\n // Neither exact field nor template options produced an ID\n throw new Error('Unable to generate element ID');\n }\n const element_id = value.toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Abstract method. It should be overridden by data layers that implement separate status\n * nodes, such as genes or intervals.\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or\n * a group of unrelated intervals (all markings grouped within a category).\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n\n // Does the data from the API satisfy the list of fields expected by this layout?\n // Not every record will have every possible field (example: left joins like assoc + ld). The check is \"did\n // we see this field at least once in any record at all\".\n if (this.data.length && this._data_contract.size) {\n const fields_unseen = new Set(this._data_contract);\n for (let record of this.data) {\n Object.keys(record).forEach((field) => fields_unseen.delete(field));\n if (!fields_unseen.size) {\n // Once every requested field has been seen in at least one record, no need to look at more records\n break;\n }\n }\n if (fields_unseen.size) {\n // Current implementation is a soft warning, so that certain \"incremental enhancement\" features\n // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info.\n // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data.\n console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in \"data_operations\" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`);\n }\n }\n\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value);\n }\n\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this._layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const _layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = _layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this._state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this._state_id] = _layer_state;\n }\n this._layer_state = _layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this._tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this._tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this._layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this._tooltips[id].selector.html('');\n this._tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this._tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this._tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this._tooltips[id]) {\n if (typeof this._tooltips[id].selector == 'object') {\n this._tooltips[id].selector.remove();\n }\n delete this._tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this._layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this._tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this._tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this._tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this._tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n const tooltip_layout = this.layout.tooltip;\n if (typeof tooltip_layout != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof tooltip_layout.show == 'string') {\n show_directive = { and: [ tooltip_layout.show ] };\n } else if (typeof tooltip_layout.show == 'object') {\n show_directive = tooltip_layout.show;\n }\n\n let hide_directive = {};\n if (typeof tooltip_layout.hide == 'string') {\n hide_directive = { and: [ tooltip_layout.hide ] };\n } else if (typeof tooltip_layout.hide == 'object') {\n hide_directive = tooltip_layout.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const _layer_state = this._layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (_layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this._layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this._layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true,\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this._layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this._layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self._state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies)\n .then((new_data) => {\n this.data = new_data;\n this.applyDataMethods();\n this._initialized = true;\n // Allow listeners (like subscribeToData) to see the information associated with a layer\n this.parent.emit(\n 'data_from_layer',\n { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin\n true,\n );\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 15,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n // FIXME: Make gene text font sizes scalable\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size,\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n tooltip: null,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this._global_statuses).forEach((global_status) => {\n if (this._global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n data_layer._label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this._label_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer._label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer._label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer._label_texts.nodes();\n data_layer._label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this._label_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this._label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this._label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this._label_texts) {\n this._label_texts.remove();\n }\n\n this._label_texts = this._label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this._label_lines) {\n this._label_lines.remove();\n }\n this._label_lines = this._label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this._label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this._label_texts) {\n this._label_texts.remove();\n }\n if (this._label_lines) {\n this._label_lines.remove();\n }\n if (this._label_groups) {\n this._label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this._label_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{assoc:variant|htmlescape}}
          \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
          \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
          \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
          `,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

          {{gene_name|htmlescape}}

          '\n + 'Gene ID: {{gene_id|htmlescape}}
          '\n + 'Transcript ID: {{transcript_id|htmlescape}}
          '\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
          ConstraintExpected variantsObserved variantsConst. Metric
          Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
          o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
          Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
          o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
          pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
          o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

          {{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{catalog:variant|htmlescape}}
          '\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
          '\n + 'Top Trait: {{catalog:trait|htmlescape}}
          '\n + 'Top P Value: {{catalog:log_pvalue|logtoscinotation}}
          '\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
          ' +\n '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
          ' +\n 'Promoter
          ' +\n '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
          ' +\n '{{#if access:target}}Target: {{access:target|htmlescape}}
          {{/if}}' +\n 'Score: {{access:score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n id: 'recombrate',\n namespace: { 'recomb': 'recomb' },\n data_operations: [\n { type: 'fetch', from: ['recomb'] },\n ],\n type: 'line',\n tag: 'recombination',\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: 'recomb:position',\n },\n y_axis: {\n axis: 2,\n field: 'recomb:recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)'],\n },\n {\n type: 'left_match',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'ld'],\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n id_field: 'assoc:variant',\n coalesce: {\n active: true,\n },\n point_shape: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 'diamond',\n },\n },\n {\n // Not every dataset will provide these params\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'assoc:beta',\n stderr_beta_field: 'assoc:se',\n },\n },\n 'circle',\n ],\n point_size: {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: 'ld:correlation',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8'\n {\n shape: 'ribbon',\n orientation: 'vertical',\n width: 10,\n height: 15,\n color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0],\n },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: 'assoc:position',\n },\n y_axis: {\n axis: 1,\n field: 'assoc:log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n namespace: { 'access': 'access' },\n data_operations: [\n { type: 'fetch', from: ['access'] },\n ],\n match: { send: 'access:target', receive: 'access:target' },\n // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.\n id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',\n filters: [\n { field: 'access:score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: 'access:start1',\n field2: 'access:start2',\n },\n y_axis: {\n axis: 1,\n field: 'access:score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n\n base.data_operations.push({\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_catalog',\n requires: ['assoc_plus_ld', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n });\n\n base.tooltip.html += '{{#if catalog:rsid}}
          See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n return base;\n}();\n\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n namespace: { 'phewas': 'phewas' },\n data_operations: [\n { type: 'fetch', from: ['phewas'] },\n ],\n point_shape: [\n {\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'phewas:beta',\n stderr_beta_field: 'phewas:se',\n },\n },\n 'circle',\n ],\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',\n x_axis: {\n field: 'lz_auto_x', // Automatically added by the category_scatter layer\n category_field: 'phewas:trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: 'phewas:log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: 'phewas:trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Trait: {{phewas:trait_label|htmlescape}}
          \nTrait Category: {{phewas:trait_group|htmlescape}}
          \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
          β: {{phewas:beta|scinotation|htmlescape}}
          {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}`,\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{phewas:trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: 'phewas:log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n data_operations: [\n {\n type: 'fetch',\n from: ['gene', 'constraint(gene)'],\n },\n {\n name: 'gene_constraint',\n type: 'genes_to_gnomad_constraint',\n requires: ['gene', 'constraint'],\n },\n ],\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n data_operations: [\n {\n type: 'fetch', from: ['assoc', 'catalog'],\n },\n {\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n },\n ],\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: '#0000CC',\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'catalog:rsid', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n },\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 300,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 46,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 75, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 40,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{catalog:trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: 'catalog:trait', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: 'ld:correlation', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '12px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 55, bottom: 20, left: 70 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu),\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 55, bottom: 120, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 55, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel),\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or applying namespaces.\n let base = super.get(type).get(name);\n\n // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.\n // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)\n const custom_namespaces = overrides.namespace;\n if (!base.namespace) {\n // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout\n // NOTE: The \"merge namespace\" behavior means that data layers can add new data easily, but this method\n // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).\n delete overrides.namespace;\n }\n let result = merge(overrides, base);\n\n if (custom_namespaces) {\n result = applyNamespaces(result, custom_namespaces);\n }\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n\n // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested\n // from external sources. This is purely a hint, because not every layout is generated through the registry.\n if (type === 'data_layer' && copy.namespace) {\n copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();\n }\n\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","/**\n * \"Data operation\" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results\n *\n * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation\n * is a \"join\", such as combining association + LD together into a single set of records for plotting. Several join\n * functions (that operate by analogy to SQL) are provided built-in.\n *\n * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):\n * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.\n * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,\n * this is the recommended path to do so)\n * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),\n * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,\n * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks\n * (PheWAS), automatic panel legends or color schemes (BED tracks), etc.\n *\n * Usually, a data operation receives two recordsets (the left and right members of the join, like \"assoc\" and \"ld\").\n * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network\n * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is\n * uncommon. (if possible, try to provide your data with fewer adapters/network requests!)\n *\n * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,\n * particularly for advanced features, may carry assumptions about field names/ formatting.\n * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,\n * or it may match two datasets based on a specific way of identifying the variant)\n *\n * @module LocusZoom_DataFunctions\n */\nimport {joins} from '../data/undercomplicate';\n\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"data join\" functions.\n * @alias module:LocusZoom~DataFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\nfunction _wrap_join(handle) {\n // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).\n\n // Many of our join functions are implemented with a different number of arguments than what a datafunction\n // actually receives. (eg, a join function is generic and doesn't care about \"context\" information like plot.state)\n // This wrapper is simple shared code to handle required validation and conversion stuff.\n return (context, deps, ...params) => {\n if (deps.length !== 2) {\n throw new Error('Join functions must receive exactly two recordsets');\n }\n return handle(...deps, ...params);\n };\n}\n\n// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to\n// pick the most significant claim in the catalog for a variant, rather than joining every possible match.\n// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.\nfunction assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {\n if (!assoc_data.length) {\n return assoc_data;\n }\n\n // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each\n const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);\n\n const catalog_flat = []; // Store only the top significant claim for each catalog variant entry\n for (let claims of catalog_by_variant.values()) {\n // Find max item within this set of claims, push that to catalog_\n let best = 0;\n let best_variant;\n for (let item of claims) {\n const val = item[catalog_logp_name];\n if ( val >= best) {\n best_variant = item;\n best = val;\n }\n }\n best_variant.n_catalog_matches = claims.length;\n catalog_flat.push(best_variant);\n }\n return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);\n}\n\n// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.\nfunction genes_to_gnomad_constraint(genes_data, constraint_data) {\n genes_data.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return genes_data;\n}\n\n\n/**\n * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset\n *\n * @function\n * @name left_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('left_match', _wrap_join(joins.left_match));\n\n/**\n * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.\n *\n * @function\n * @name inner_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('inner_match', _wrap_join(joins.inner_match));\n\n/**\n * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.\n *\n * @function\n * @name full_outer_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('full_outer_match', _wrap_join(joins.full_outer_match));\n\n/**\n * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.\n *\n * @function\n * @name assoc_to_gwas_catalog\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: assoc records, then catalog records\n * @param {String} assoc_key The name of the key field in association data, eg variant ID\n * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID\n * @param {String} catalog_log_p_name The name of the \"log_pvalue\" field in gwas catalog data, used to choose the most significant claim for a given variant\n */\nregistry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));\n\n/**\n * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).\n *\n * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.\n *\n * @function\n * @name genes_to_gnomad_constraint\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data\n */\nregistry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));\n\nexport default registry;\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n DATA_OPS as DataFunctions,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n WIDGETS as Widgets,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n DataFunctions,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/./node_modules/@hapi/hoek/lib/assert.js","webpack://[name]/./node_modules/@hapi/hoek/lib/error.js","webpack://[name]/./node_modules/@hapi/hoek/lib/stringify.js","webpack://[name]/./node_modules/@hapi/topo/lib/index.js","webpack://[name]/./node_modules/just-clone/index.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./esm/data/undercomplicate/lru_cache.js","webpack://[name]/./esm/data/undercomplicate/util.js","webpack://[name]/./esm/data/undercomplicate/requests.js","webpack://[name]/./esm/data/undercomplicate/joins.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./esm/data/undercomplicate/adapter.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/registry/data_ops.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["AssertError","module","exports","condition","args","length","Error","Stringify","super","filter","arg","map","message","join","captureStackTrace","this","assert","JSON","stringify","apply","err","Assert","internals","_items","nodes","options","before","concat","after","group","sort","includes","Array","isArray","node","item","seq","push","manual","valid","_sort","others","other","Object","assign","mergeSort","i","graph","graphAfters","create","groups","expandedGroups","graphNodeItem","ancestors","children","child","visited","sorted","next","j","shouldSeeCount","seenCount","k","seqIndex","value","sortedItem","a","b","getRegExpFlags","regExp","source","flags","global","ignoreCase","multiline","sticky","unicode","clone","obj","result","key","type","toString","call","slice","Date","getTime","RegExp","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","prop","prototype","hasOwnProperty","r","Symbol","toStringTag","RegistryBase","Map","name","has","override","set","delete","from","keys","ClassRegistry","parent_name","source_name","overrides","console","warn","arguments","base","sub","add","LLNode","metadata","prev","LRUCache","max_size","_max_size","_cur_size","_store","_head","_tail","cached","prior","_remove","old","match_callback","data","getLinkedData","shared_options","entities","dependencies","consolidate","parsed","spec","exec","name_alone","name_deps","deps","split","_parse_declaration","dag","toposort","entries","e","order","responses","provider","depends_on","this_result","Promise","all","then","prior_results","_provider_name","getData","values","all_results","groupBy","records","group_key","item_group","_any_match","left","right","left_key","right_key","right_index","results","left_match_value","right_matches","right_item","left_index","right_match_value","left_match","REGEX_MARKER","parseMarker","test","match","BaseApiAdapter","BaseLZAdapter","config","_config","cache_enabled","cache_size","_enable_cache","_cache","dependent_data","response_text","_buildRequestOptions","cache_key","_getCacheKey","resolve","_performRequest","text","_normalizeResponse","_cache_meta","catch","remove","_annotateRecords","_postProcessResponse","_url","url","_getURL","fetch","response","ok","statusText","parse","params","prefix_namespace","limit_fields","_prefix_namespace","_limit_fields","Set","chr","start","end","superset","find","md","row","reduce","acc","label","a_record","fieldname","suffixer","BaseUMAdapter","_genome_build","genome_build","build","constructor","N","every","fields","record","AssociationLZ","_source_id","request_options","GwasCatalogLZ","_validateBuildSource","source_query","GeneLZ","GeneConstraintLZ","state","genes_data","unique_gene_names","gene","gene_name","query","replace","body","method","headers","LDServer","assoc_data","assoc_variant_name","_findPrefixedKey","assoc_logp_name","refvar","best_hit","ldrefvar","best_logp","variant","log_pvalue","lz_is_ld_refvar","chrom","pos","ref","alt","coord","String","__find_ld_refvar","_skip_request","ld_refvar","ld_source","ld_population","ld_pop","population","encodeURIComponent","combined","chainRequests","payload","forEach","RecombLZ","StaticSource","_data","PheWASLZ","registry","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","transforms","full_name","field_name","transformations","val","transform","extra","undefined","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","m","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","paths","p","_","__","subject","uniqPaths","arr","elem","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","layout","shared_namespaces","requested_ns","merge","custom_layout","default_layout","property","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","findFields","prefixes","field_finder","all_ns","value_type","a_match","renameField","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","regex","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","DataOperation","join_type","initiator","_callable","_initiator","_params","plot_state","dependent_recordsets","data_layer","sources","_sources","namespace_options","data_operations","namespace_local_names","dependency_order","unshift","ns_pattern","local_name","global_name","dep_spec","requires","namecount","require_name","task","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","parentNode","insert","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","x","width","delay","setTimeout","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","position","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","index","indexOf","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","element","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","_panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","radioId","has_option","choice","defaultName","default_config_display_name","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","addWidget","widget","error","_panel_boundaries","dragging","_interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","line_height","_data_layer_ids_by_z_index","reverse","layer_legend","label_x","label_y","shape_factory","path_y","is_horizontal","color_stops","all_elements","ribbon_group","axis_group","axis_offset","tick_labels","range","scale","domain","axis","tickSize","tickValues","tickFormat","v","to_next_marking","radius","PI","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","_initialized","_layout_idx","_state_id","_data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","_zoom_timeout","_event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","parseFloat","y_axis","z_index","dlid","idx","layout_idx","data_layer_layout","target_layer","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","axes_config","base_x_range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","current_drag","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","namespace","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tick_format","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","panel_boundaries","mouse_guide","Plot","datasource","_remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","_layer_state","_setDefaultState","target_panel","clearPanelData","opts","success_callback","from_layer","onerror","error_callback","base_prefix","layer_target","startsWith","is_valid_layer","some","listener","config_to_sources","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","mutateLayout","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","resize_listener","window","requestAnimationFrame","rescaleSVG","addEventListener","trackExternalListener","IntersectionObserver","threshold","observer","entry","intersectionRatio","observe","load_listener","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","_mouse_guide","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","ret","positionStringToInt","suffixre","mult","tokens","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","field_value","numerical_bin","breaks","null_value","curr","categorical_bin","categories","ordinal_cycle","stable_choice","max_cache_size","clear","hash","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","effect_direction","input","beta_field","stderr_beta_field","plus_result","neg_result","beta_val","se_val","id_field","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","_tooltips","_global_statuses","_data_contract","_entities","_dependencies","layer_order","current_index","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","fields_unseen","debug","lz_is_match","getDataLayer","getPanel","getPlot","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","tooltip_layout","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","_label_texts","da","dal","_label_lines","dax","abound","bbound","_label_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","_label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","version","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","custom_namespaces","_auto_fields","contents","list","_wrap_join","handle","catalog_data","assoc_key","catalog_key","catalog_logp_name","catalog_by_variant","catalog_flat","claims","best_variant","best","n_catalog_matches","constraint_data","alias","constraint","LocusZoom","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","source_id","Adapters","DataLayers","DataFunctions","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["'use strict';\n\nconst AssertError = require('./error');\n\nconst internals = {};\n\n\nmodule.exports = function (condition, ...args) {\n\n if (condition) {\n return;\n }\n\n if (args.length === 1 &&\n args[0] instanceof Error) {\n\n throw args[0];\n }\n\n throw new AssertError(args);\n};\n","'use strict';\n\nconst Stringify = require('./stringify');\n\n\nconst internals = {};\n\n\nmodule.exports = class extends Error {\n\n constructor(args) {\n\n const msgs = args\n .filter((arg) => arg !== '')\n .map((arg) => {\n\n return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);\n });\n\n super(msgs.join(' ') || 'Unknown error');\n\n if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$\n Error.captureStackTrace(this, exports.assert);\n }\n }\n};\n","'use strict';\n\nconst internals = {};\n\n\nmodule.exports = function (...args) {\n\n try {\n return JSON.stringify.apply(null, args);\n }\n catch (err) {\n return '[Cannot display object: ' + err.message + ']';\n }\n};\n","'use strict';\n\nconst Assert = require('@hapi/hoek/lib/assert');\n\n\nconst internals = {};\n\n\nexports.Sorter = class {\n\n constructor() {\n\n this._items = [];\n this.nodes = [];\n }\n\n add(nodes, options) {\n\n options = options || {};\n\n // Validate rules\n\n const before = [].concat(options.before || []);\n const after = [].concat(options.after || []);\n const group = options.group || '?';\n const sort = options.sort || 0; // Used for merging only\n\n Assert(!before.includes(group), `Item cannot come before itself: ${group}`);\n Assert(!before.includes('?'), 'Item cannot come before unassociated items');\n Assert(!after.includes(group), `Item cannot come after itself: ${group}`);\n Assert(!after.includes('?'), 'Item cannot come after unassociated items');\n\n if (!Array.isArray(nodes)) {\n nodes = [nodes];\n }\n\n for (const node of nodes) {\n const item = {\n seq: this._items.length,\n sort,\n before,\n after,\n group,\n node\n };\n\n this._items.push(item);\n }\n\n // Insert event\n\n if (!options.manual) {\n const valid = this._sort();\n Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');\n }\n\n return this.nodes;\n }\n\n merge(others) {\n\n if (!Array.isArray(others)) {\n others = [others];\n }\n\n for (const other of others) {\n if (other) {\n for (const item of other._items) {\n this._items.push(Object.assign({}, item)); // Shallow cloned\n }\n }\n }\n\n // Sort items\n\n this._items.sort(internals.mergeSort);\n for (let i = 0; i < this._items.length; ++i) {\n this._items[i].seq = i;\n }\n\n const valid = this._sort();\n Assert(valid, 'merge created a dependencies error');\n\n return this.nodes;\n }\n\n sort() {\n\n const valid = this._sort();\n Assert(valid, 'sort created a dependencies error');\n\n return this.nodes;\n }\n\n _sort() {\n\n // Construct graph\n\n const graph = {};\n const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives\n const groups = Object.create(null);\n\n for (const item of this._items) {\n const seq = item.seq; // Unique across all items\n const group = item.group;\n\n // Determine Groups\n\n groups[group] = groups[group] || [];\n groups[group].push(seq);\n\n // Build intermediary graph using 'before'\n\n graph[seq] = item.before;\n\n // Build second intermediary graph with 'after'\n\n for (const after of item.after) {\n graphAfters[after] = graphAfters[after] || [];\n graphAfters[after].push(seq);\n }\n }\n\n // Expand intermediary graph\n\n for (const node in graph) {\n const expandedGroups = [];\n\n for (const graphNodeItem in graph[node]) {\n const group = graph[node][graphNodeItem];\n groups[group] = groups[group] || [];\n expandedGroups.push(...groups[group]);\n }\n\n graph[node] = expandedGroups;\n }\n\n // Merge intermediary graph using graphAfters into final graph\n\n for (const group in graphAfters) {\n if (groups[group]) {\n for (const node of groups[group]) {\n graph[node].push(...graphAfters[group]);\n }\n }\n }\n\n // Compile ancestors\n\n const ancestors = {};\n for (const node in graph) {\n const children = graph[node];\n for (const child of children) {\n ancestors[child] = ancestors[child] || [];\n ancestors[child].push(node);\n }\n }\n\n // Topo sort\n\n const visited = {};\n const sorted = [];\n\n for (let i = 0; i < this._items.length; ++i) { // Looping through item.seq values out of order\n let next = i;\n\n if (ancestors[i]) {\n next = null;\n for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values\n if (visited[j] === true) {\n continue;\n }\n\n if (!ancestors[j]) {\n ancestors[j] = [];\n }\n\n const shouldSeeCount = ancestors[j].length;\n let seenCount = 0;\n for (let k = 0; k < shouldSeeCount; ++k) {\n if (visited[ancestors[j][k]]) {\n ++seenCount;\n }\n }\n\n if (seenCount === shouldSeeCount) {\n next = j;\n break;\n }\n }\n }\n\n if (next !== null) {\n visited[next] = true;\n sorted.push(next);\n }\n }\n\n if (sorted.length !== this._items.length) {\n return false;\n }\n\n const seqIndex = {};\n for (const item of this._items) {\n seqIndex[item.seq] = item;\n }\n\n this._items = [];\n this.nodes = [];\n\n for (const value of sorted) {\n const sortedItem = seqIndex[value];\n this.nodes.push(sortedItem.node);\n this._items.push(sortedItem);\n }\n\n return true;\n }\n};\n\n\ninternals.mergeSort = (a, b) => {\n\n return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);\n};\n","module.exports = clone;\n\n/*\n Deep clones all properties except functions\n\n var arr = [1, 2, 3];\n var subObj = {aa: 1};\n var obj = {a: 3, b: 5, c: arr, d: subObj};\n var objClone = clone(obj);\n arr.push(4);\n subObj.bb = 2;\n obj; // {a: 3, b: 5, c: [1, 2, 3, 4], d: {aa: 1}}\n objClone; // {a: 3, b: 5, c: [1, 2, 3], d: {aa: 1, bb: 2}}\n*/\n\nfunction clone(obj) {\n if (typeof obj == 'function') {\n return obj;\n }\n var result = Array.isArray(obj) ? [] : {};\n for (var key in obj) {\n // include prototype properties\n var value = obj[key];\n var type = {}.toString.call(value).slice(8, -1);\n if (type == 'Array' || type == 'Object') {\n result[key] = clone(value);\n } else if (type == 'Date') {\n result[key] = new Date(value.getTime());\n } else if (type == 'RegExp') {\n result[key] = RegExp(value.source, getRegExpFlags(value));\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction getRegExpFlags(regExp) {\n if (typeof regExp.source.flags == 'string') {\n return regExp.source.flags;\n } else {\n var flags = [];\n regExp.global && flags.push('g');\n regExp.ignoreCase && flags.push('i');\n regExp.multiline && flags.push('m');\n regExp.sticky && flags.push('y');\n regExp.unicode && flags.push('u');\n return flags.join('');\n }\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.14.0-beta.4';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","/**\n * Implement an LRU Cache\n */\n\n\nclass LLNode {\n /**\n * A single node in the linked list. Users will only need to deal with this class if using \"approximate match\" (`cache.find()`)\n * @memberOf module:undercomplicate\n * @param {string} key\n * @param {*} value\n * @param {object} metadata\n * @param {LLNode} prev\n * @param {LLNode} next\n */\n constructor(key, value, metadata = {}, prev = null, next = null) {\n this.key = key;\n this.value = value;\n this.metadata = metadata;\n this.prev = prev;\n this.next = next;\n }\n}\n\nclass LRUCache {\n /**\n * Least-recently used cache implementation, with \"approximate match\" semantics\n * @memberOf module:undercomplicate\n * @param {number} [max_size=3]\n */\n constructor(max_size = 3) {\n this._max_size = max_size;\n this._cur_size = 0; // replace with map.size so we aren't managing manually?\n this._store = new Map();\n\n // Track LRU state\n this._head = null;\n this._tail = null;\n\n // Validate options\n if (max_size === null || max_size < 0) {\n throw new Error('Cache \"max_size\" must be >= 0');\n }\n }\n\n /**\n * Check key membership without updating LRU\n * @param key\n * @returns {boolean}\n */\n has(key) {\n return this._store.has(key);\n }\n\n /**\n * Retrieve value from cache (if present) and update LRU cache to say an item was recently used\n * @param key\n * @returns {null|*}\n */\n get(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return null;\n }\n if (this._head !== cached) {\n // Rewrite the cached value to ensure it is head of the list\n this.add(key, cached.value);\n }\n return cached.value;\n }\n\n /**\n * Add an item. Forcibly replaces the existing cached value for the same key.\n * @param key\n * @param value\n * @param {Object} [metadata={}) Metadata associated with an item. Metadata can be used for lookups (`cache.find`) to test for a cache hit based on non-exact match\n */\n add(key, value, metadata = {}) {\n if (this._max_size === 0) {\n // Don't cache items if cache has 0 size.\n return;\n }\n\n const prior = this._store.get(key);\n if (prior) {\n this._remove(prior);\n }\n\n const node = new LLNode(key, value, metadata, null, this._head);\n\n if (this._head) {\n this._head.prev = node;\n } else {\n this._tail = node;\n }\n\n this._head = node;\n this._store.set(key, node);\n\n if (this._max_size >= 0 && this._cur_size >= this._max_size) {\n const old = this._tail;\n this._tail = this._tail.prev;\n this._remove(old);\n }\n this._cur_size += 1;\n }\n\n\n // Cache manipulation methods\n clear() {\n this._head = null;\n this._tail = null;\n this._cur_size = 0;\n this._store = new Map();\n }\n\n // Public method, remove by key\n remove(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return false;\n }\n this._remove(cached);\n return true;\n }\n\n // Internal implementation, useful when working on list\n _remove(node) {\n if (node.prev !== null) {\n node.prev.next = node.next;\n } else {\n this._head = node.next;\n }\n\n if (node.next !== null) {\n node.next.prev = node.prev;\n } else {\n this._tail = node.prev;\n }\n this._store.delete(node.key);\n this._cur_size -= 1;\n }\n\n /**\n * Find a matching item in the cache, or return null. This is useful for \"approximate match\" semantics,\n * to check if something qualifies as a cache hit despite not having the exact same key.\n * (Example: zooming into a region, where the smaller region is a subset of something already cached)\n * @param {function} match_callback A function to be used to test the node as a possible match (returns true or false)\n * @returns {null|LLNode}\n */\n find(match_callback) {\n let node = this._head;\n while (node) {\n const next = node.next;\n if (match_callback(node)) {\n return node;\n }\n node = next;\n }\n }\n}\n\nexport { LRUCache };\n","/**\n * @private\n */\n\nimport justclone from 'just-clone';\n\n/**\n * The \"just-clone\" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.\n * @param {object} data\n * @returns {*}\n */\nfunction clone(data) {\n if (typeof data !== 'object') {\n return data;\n }\n return justclone(data);\n}\n\nexport { clone };\n","/**\n * Perform a series of requests, respecting order of operations\n * @private\n */\n\nimport {Sorter} from '@hapi/topo';\n\n\nfunction _parse_declaration(spec) {\n // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.\n const parsed = /^(?\\w+)$|((?\\w+)+\\(\\s*(?[^)]+?)\\s*\\))/.exec(spec);\n if (!parsed) {\n throw new Error(`Unable to parse dependency specification: ${spec}`);\n }\n\n let {name_alone, name_deps, deps} = parsed.groups;\n if (name_alone) {\n return [name_alone, []];\n }\n\n deps = deps.split(/\\s*,\\s*/);\n return [name_deps, deps];\n}\n\n/**\n * Perform a request for data from a set of providers, taking into account dependencies between requests.\n * This can be a mix of network requests or other actions (like join tasks), provided that the provider be some\n * object that implements a method `instance.getData`\n *\n * Each data layer in LocusZoom will translate the internal configuration into a format used by this function.\n * This function is never called directly in custom user code. In locuszoom, Requester Handles You\n *\n * TODO Future: It would be great to add a warning if the final element in the DAG does not reference all dependencies. This is a limitation of the current toposort library we use.\n *\n * @param {object} shared_options Options passed globally to all requests. In LocusZoom, this is often a copy of \"plot.state\"\n * @param {Map} entities A lookup of named entities that implement the method `instance.getData -> Promise`\n * @param {String[]} dependencies A description of how to fetch entities, and what they depend on, like `['assoc', 'ld(assoc)']`.\n * **Order will be determined by a DAG and the last item in the DAG is all that is returned.**\n * @param {boolean} [consolidate=true] Whether to return all results (almost never used), or just the last item in the resolved DAG.\n * This can be a pitfall in common usage: if you forget a \"join/consolidate\" task, the last result may appear to be missing some data.\n * @returns {Promise}\n */\nfunction getLinkedData(shared_options, entities, dependencies, consolidate = true) {\n if (!dependencies.length) {\n return [];\n }\n\n const parsed = dependencies.map((spec) => _parse_declaration(spec));\n const dag = new Map(parsed);\n\n // Define the order to perform requests in, based on a DAG\n const toposort = new Sorter();\n for (let [name, deps] of dag.entries()) {\n try {\n toposort.add(name, {after: deps, group: name});\n } catch (e) {\n throw new Error(`Invalid or possible circular dependency specification for: ${name}`);\n }\n }\n const order = toposort.nodes;\n\n // Verify that all requested entities exist by name!\n const responses = new Map();\n for (let name of order) {\n const provider = entities.get(name);\n if (!provider) {\n throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);\n }\n\n // Each promise should only be triggered when the things it depends on have been resolved\n const depends_on = dag.get(name) || [];\n const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));\n\n const this_result = prereq_promises.then((prior_results) => {\n // Each request will be told the name of the provider that requested it. This can be used during post-processing,\n // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)\n // This has a secondary effect: it ensures that any changes made to \"shared\" options in one adapter will\n // not leak out to others via a mutable shared object reference.\n const options = Object.assign({_provider_name: name}, shared_options);\n return provider.getData(options, ...prior_results);\n });\n responses.set(name, this_result);\n }\n return Promise.all([...responses.values()])\n .then((all_results) => {\n if (consolidate) {\n // Some usages- eg fetch + data join tasks- will only require the last response in the sequence\n // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)\n return all_results[all_results.length - 1];\n }\n return all_results;\n });\n}\n\n\nexport {getLinkedData};\n\n// For testing only\nexport {_parse_declaration};\n","/**\n * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.\n */\nimport { clone } from './util';\n\n\n/**\n * Simple grouping function, used to identify sets of records for joining.\n *\n * Used internally by join helpers, exported mainly for unit testing\n * @memberOf module:undercomplicate\n * @param {object[]} records\n * @param {string} group_key\n * @returns {Map}\n */\nfunction groupBy(records, group_key) {\n const result = new Map();\n for (let item of records) {\n const item_group = item[group_key];\n\n if (typeof item_group === 'undefined') {\n throw new Error(`All records must specify a value for the field \"${group_key}\"`);\n }\n if (typeof item_group === 'object') {\n // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)\n throw new Error('Attempted to group on a field with non-primitive values');\n }\n\n let group = result.get(item_group);\n if (!group) {\n group = [];\n result.set(item_group, group);\n }\n group.push(item);\n }\n return result;\n}\n\n\nfunction _any_match(type, left, right, left_key, right_key) {\n // Helper that consolidates logic for all three join types\n const right_index = groupBy(right, right_key);\n const results = [];\n for (let item of left) {\n const left_match_value = item[left_key];\n const right_matches = right_index.get(left_match_value) || [];\n if (right_matches.length) {\n // Record appears on both left and right; equiv to an inner join\n results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));\n } else if (type !== 'inner') {\n // Record appears on left but not right\n results.push(clone(item));\n }\n }\n\n if (type === 'outer') {\n // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side\n const left_index = groupBy(left, left_key);\n for (let item of right) {\n const right_match_value = item[right_key];\n const left_matches = left_index.get(right_match_value) || [];\n if (!left_matches.length) {\n results.push(clone(item));\n }\n }\n }\n return results;\n}\n\n/**\n * Equivalent to LEFT OUTER JOIN in SQL. Return all left records, joined to matching right data where appropriate.\n * @memberOf module:undercomplicate\n * @param {Object[]} left The left side recordset\n * @param {Object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction left_match(left, right, left_key, right_key) {\n return _any_match('left', ...arguments);\n}\n\n/**\n * Equivalent to INNER JOIN in SQL. Only return record joins if the key field has a match on both left and right.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction inner_match(left, right, left_key, right_key) {\n return _any_match('inner', ...arguments);\n}\n\n/**\n * Equivalent to FULL OUTER JOIN in SQL. Return records in either recordset, joined where appropriate.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction full_outer_match(left, right, left_key, right_key) {\n return _any_match('outer', ...arguments);\n}\n\nexport {left_match, inner_match, full_outer_match, groupBy};\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 1 }]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 2 }]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nimport {BaseUrlAdapter} from './undercomplicate';\n\nimport {parseMarker} from '../helpers/parse';\n\n/**\n * Replaced with the BaseLZAdapter class.\n * @public\n * @deprecated\n */\nclass BaseAdapter {\n constructor() {\n throw new Error('The \"BaseAdapter\" and \"BaseApiAdapter\" classes have been replaced in LocusZoom 0.14. See migration guide for details.');\n }\n}\n\n/**\n * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @deprecated\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {}\n\n\n/**\n * @extends module:undercomplicate.BaseUrlAdapter\n * @inheritDoc\n */\nclass BaseLZAdapter extends BaseUrlAdapter {\n /**\n * @param [config.cache_enabled=true]\n * @param [config.cache_size=3]\n * @param [config.url]\n * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name.\n * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant)\n * Typically, this is only disabled if the response payload is very unusual\n * @param {String[]} [config.limit_fields=null] If an API returns far more data than is needed, this can be used to simplify\n * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.\n */\n constructor(config = {}) {\n if (config.params) {\n // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places.\n console.warn('Deprecation warning: all options in \"config.params\" should now be specified as top level keys.');\n Object.assign(config, config.params || {});\n delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality!\n }\n super(config);\n\n // Prefix the namespace for this source to all fieldnames: id -> assoc.id\n // This is useful for almost all layers because the layout object says where to find every field, exactly.\n // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on\n // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear\n // in the response. (gene_name instead of genes.gene_name)\n const { prefix_namespace = true, limit_fields } = config;\n this._prefix_namespace = prefix_namespace;\n this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields\n }\n\n /**\n * Determine how a particular request will be identified in cache. Most LZ requests are region based,\n * so the default is a string concatenation of `chr_start_end`. This adapter is \"region aware\"- if the user\n * zooms in, it won't trigger a network request because we alread have the data needed.\n * @param options Receives plot.state plus any other request options defined by this source\n * @returns {string}\n * @public\n */\n _getCacheKey(options) {\n // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default\n let {chr, start, end} = options; // Current view: plot.state\n\n // Does a prior cache hit qualify as a superset of the ROI?\n const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end);\n if (superset) {\n ({ chr, start, end } = superset.metadata);\n }\n\n // The default cache key is region-based, and this method only returns the region-based part of the cache hit\n // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check\n options._cache_meta = { chr, start, end };\n return `${chr}_${start}_${end}`;\n }\n\n /**\n * Add the \"local namespace\" as a prefix for every field returned for this request. Eg if the association api\n * returns a field called variant, and the source is referred to as \"assoc\" within a particular data layer, then\n * the returned records will have a field called \"assoc:variant\"\n *\n * @param records\n * @param options\n * @returns {*}\n * @public\n */\n _postProcessResponse(records, options) {\n if (!this._prefix_namespace || !Array.isArray(records)) {\n return records;\n }\n\n // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for\n // assoc data might see \"variant\" as \"assoc.variant\"\n const { _limit_fields } = this;\n const { _provider_name } = options;\n\n return records.map((row) => {\n return Object.entries(row).reduce(\n (acc, [label, value]) => {\n // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload.\n if (!_limit_fields || _limit_fields.has(label)) {\n acc[`${_provider_name}:${label}`] = value;\n }\n return acc;\n },\n {},\n );\n });\n }\n\n /**\n * Convenience method, manually called in LZ sources that deal with dependent data.\n *\n * In the last step of fetching data, LZ adds a prefix to each field name.\n * This means that operations like \"build query based on prior data\" can't just ask for \"log_pvalue\" because\n * they are receiving \"assoc:log_pvalue\" or some such unknown prefix.\n *\n * This helper lets us use dependent data more easily. Not every adapter needs to use this method.\n *\n * @param {Object} a_record One record (often the first one in a set of records)\n * @param {String} fieldname The desired fieldname, eg \"log_pvalue\"\n */\n _findPrefixedKey(a_record, fieldname) {\n const suffixer = new RegExp(`:${fieldname}$`);\n const match = Object.keys(a_record).find((key) => suffixer.test(key));\n if (!match) {\n throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`);\n }\n return match;\n }\n}\n\n\n/**\n * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies\n * of one particular web server.\n * @extends module:LocusZoom_Adapters~BaseLZAdapter\n * @inheritDoc\n */\nclass BaseUMAdapter extends BaseLZAdapter {\n /**\n * @param {Object} config\n * @param {String} [config.build] The genome build to be used by all requests for this adapter. (UMich APIs are all genome build aware). \"GRCh37\" or \"GRCh38\"\n */\n constructor(config = {}) {\n super(config);\n // The UM portaldev API accepts an (optional) parameter \"genome_build\"\n this._genome_build = config.genome_build || config.build;\n }\n\n _validateBuildSource(build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${this.constructor.name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`);\n }\n }\n\n // Special behavior for the UM portaldev API: col -> row format normalization\n /**\n * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.\n * @param response_text\n * @param options\n * @returns {Object[]}\n * @public\n */\n _normalizeResponse(response_text, options) {\n let data = super._normalizeResponse(...arguments);\n // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload\n data = data.data || data;\n\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the columns, and create an object for each row record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n}\n\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @inheritDoc\n *\n * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL\n */\nclass AssociationLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files\n const { source } = config;\n this._source_id = source;\n }\n\n _getURL (request_options) {\n const {chr, start, end} = request_options;\n const base = super._getURL(request_options);\n return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`;\n }\n}\n\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GwasCatalogLZ extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build] The genome build to use when requesting the specific genomic region.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n const source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given.\n // We will avoid transforming or modifying the payload.\n this._prefix_namespace = false;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GeneConstraintLZ extends BaseLZAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config = {}) {\n super(config);\n this._prefix_namespace = false;\n }\n\n _buildRequestOptions(state, genes_data) {\n const build = state.genome_build || this._config.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = new Set();\n for (let gene of genes_data) {\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n unique_gene_names.add(gene.gene_name);\n }\n\n state.query = [...unique_gene_names.values()].map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n state.build = build;\n return Object.assign({}, state);\n }\n\n _performRequest(options) {\n let {query, build} = options;\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears to set max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve([]);\n }\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n\n const url = this._getURL(options);\n\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.\n */\n _normalizeResponse(response_text) {\n if (typeof response_text !== 'string') {\n // If the query short-circuits, we receive an empty list instead of a string\n return response_text;\n }\n const data = JSON.parse(response_text);\n return data.data;\n }\n}\n\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant and yse that as the LD reference variant.\n *\n * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly\n * if this information is not provided.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass LDServer extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.method='rsquare'] The metric used to calculate LD\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n __find_ld_refvar(state, assoc_data) {\n const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant');\n const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue');\n\n // Determine the reference variant (via user selected OR automatic-per-track)\n let refvar;\n let best_hit = {};\n if (state.ldrefvar) {\n // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data\n refvar = state.ldrefvar;\n best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {};\n } else {\n // find highest log-value and associated var spec\n let best_logp = 0;\n for (let item of assoc_data) {\n const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item;\n if (log_pvalue > best_logp) {\n best_logp = log_pvalue;\n refvar = variant;\n best_hit = item;\n }\n }\n }\n\n // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting.\n // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase.\n best_hit.lz_is_ld_refvar = true;\n\n // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server,\n // the variant fields must be normalized to a specific format. All later LD operations will use that format.\n const match = parseMarker(refvar, true);\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n\n const [chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip?\n if (ref && alt) {\n refvar += `_${ref}/${alt}`;\n }\n\n const coord = +pos;\n // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably\n // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby.\n if ((coord && state.ldrefvar && state.chr) && (chrom !== String(state.chr) || coord < state.start || coord > state.end)) {\n // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a\n // *copy* of plot.state, so wiping here doesn't remove the original value.\n state.ldrefvar = null;\n return this.__find_ld_refvar(state, assoc_data);\n }\n\n // Return the reference variant, in a normalized format suitable for LDServer queries\n return refvar;\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n // No variants, so no need to annotate association data with LD!\n // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region)\n base._skip_request = true;\n return base;\n }\n\n base.ld_refvar = this.__find_ld_refvar(state, assoc_data);\n\n // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config\n const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let ld_source = state.ld_source || this._config.source || '1000G';\n const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL\n\n if (ld_source === '1000G' && genome_build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n ld_source = '1000G-FRZ09';\n }\n\n this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option\n return Object.assign({}, base, { genome_build, ld_source, ld_population });\n }\n\n _getURL(request_options) {\n const method = this._config.method || 'rsquare';\n const {\n chr, start, end,\n ld_refvar,\n genome_build, ld_source, ld_population,\n } = request_options;\n\n const base = super._getURL(request_options);\n\n return [\n base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(ld_refvar),\n '&chrom=', encodeURIComponent(chr),\n '&start=', encodeURIComponent(start),\n '&stop=', encodeURIComponent(end),\n ].join('');\n }\n\n _getCacheKey(options) {\n // LD is keyed by more than just region; append other parameters to the base cache key\n const base = super._getCacheKey(options);\n const { ld_refvar, ld_source, ld_population } = options;\n return `${base}_${ld_refvar}_${ld_source}_${ld_population}`;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird.\n return Promise.resolve([]);\n }\n\n const url = this._getURL(options);\n\n // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseUMAdapter {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['position', 'recomb_rate'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {object} config.data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseLZAdapter {\n constructor(config = {}) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n super(...arguments);\n const { data } = config;\n if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key\n throw new Error(\"'StaticSource' must provide data as required option 'config.data'\");\n }\n this._data = data;\n }\n\n _performRequest(options) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will\n * be used to provide PheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const base = super._getURL(request_options);\n const url = [\n base,\n \"?filter=variant eq '\", encodeURIComponent(request_options.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n _getCacheKey(options) {\n // Not a region based source; don't do anything smart for cache check\n return this._getURL(options);\n }\n}\n\n// Deprecated symbols\nexport { BaseAdapter, BaseApiAdapter };\n\n// Usually used as a parent class for custom code\nexport { BaseLZAdapter, BaseUMAdapter };\n\n// Usually used as a standalone class\nexport {\n AssociationLZ,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","import {LRUCache} from './lru_cache';\nimport {clone} from './util';\n\n/**\n * @param {boolean} [config.cache_enabled=true] Whether to enable the LRU cache, and store a copy of the normalized and parsed response data.\n * Turned on by default for most remote requests; turn off if you are using another datastore (like Vuex) or if the response body uses too much memory.\n * @param {number} [config.cache_size=3] How many requests to cache. Track-dependent annotations like LD might benefit\n * from caching more items, while very large payloads (like, um, TOPMED LD) might benefit from a smaller cache size.\n * For most LocusZoom usages, the cache is \"region aware\": zooming in will use cached data, not a separate request\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseAdapter {\n constructor(config = {}) {\n this._config = config;\n const {\n // Cache control\n cache_enabled = true,\n cache_size = 3,\n } = config;\n this._enable_cache = cache_enabled;\n this._cache = new LRUCache(cache_size);\n }\n\n /**\n * Build an object with options that control the request. This can take into account both explicit options, and prior data.\n * @param {Object} options Any global options passed in via `getData`. Eg, in locuszoom, every request is passed a copy of `plot.state` as the options object, in which case every adapter would expect certain basic information like `chr, start, end` to be available.\n * @param {Object[]} dependent_data If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, `ld(assoc)` means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.\n * @returns {*} An options object containing initial options, plus any calculated values relevant to the request.\n * @public\n */\n _buildRequestOptions(options, dependent_data) {\n // Perform any pre-processing required that may influence the request. Receives an array with the payloads\n // for each request that preceded this one in the dependency chain\n // This method may optionally take dependent data into account. For many simple adapters, there won't be any dependent data!\n return Object.assign({}, options);\n }\n\n /**\n * Determine how this request is uniquely identified in cache. Usually this is an exact match for the same key, but it doesn't have to be.\n * The LRU cache implements a `find` method, which means that a cache item can optionally be identified by its node\n * `metadata` (instead of exact key match).\n * This is useful for situations where the user zooms in to a smaller region and wants the original request to\n * count as a cache hit. See subclasses for example.\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {*} This is often a string concatenating unique values for a compound cache key, like `chr_start_end`. If null, it is treated as a cache miss.\n * @public\n */\n _getCacheKey(options) {\n /* istanbul ignore next */\n if (this._enable_cache) {\n throw new Error('Method not implemented');\n }\n return null;\n }\n\n /**\n * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {Promise}\n * @public\n */\n _performRequest(options) {\n /* istanbul ignore next */\n throw new Error('Not implemented');\n }\n\n /**\n * Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.\n * @param {*} response_text The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON.\n * @param {Object} options Request options. These are not typically used when normalizing a response, but the object is available.\n * @returns {*} A list of objects, each object representing one row of data `{column_name: value_for_row}`\n * @public\n */\n _normalizeResponse(response_text, options) {\n return response_text;\n }\n\n /**\n * Perform custom client-side operations on the retrieved data. For example, add calculated fields or\n * perform rapid client-side filtering on cached data. Annotations are applied after cache, which means\n * that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.\n *\n * This result is currently not cached, but it may become so in the future as responsibility for dynamic UI\n * behavior moves to other layers of the application.\n * @param {Object[]} records\n * @param {Object} options\n * @returns {*}\n * @public\n */\n _annotateRecords(records, options) {\n return records;\n }\n\n /**\n * A hook to transform the response after all operations are done. For example, this can be used to prefix fields\n * with a namespace unique to the request, like `log_pvalue` -> `assoc:log_pvalue`. (by applying namespace prefixes to field names last,\n * annotations and validation can happen on the actual API payload, without having to guess what the fields were renamed to).\n * @param records\n * @param options\n * @public\n */\n _postProcessResponse(records, options) {\n return records;\n }\n\n /**\n * All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.\n * @param {object} options Shared options for this request. In LocusZoom, this is typically a copy of `plot.state`.\n * @param {Array[]} dependent_data Zero or more recordsets corresponding to each individual adapter that this one depends on.\n * Can be used to build a request that takes into account prior data.\n * @returns {Promise<*>}\n */\n getData(options = {}, ...dependent_data) {\n // Public facing method to define, perform, and process the request\n options = this._buildRequestOptions(options, ...dependent_data);\n\n const cache_key = this._getCacheKey(options);\n\n // Then retrieval and parse steps: parse + normalize response, annotate\n let result;\n if (this._enable_cache && this._cache.has(cache_key)) {\n result = this._cache.get(cache_key);\n } else {\n // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)\n // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry\n // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,\n // even if the actual cache key wasn't an exact match. (see subclasses for an example; this class is generic)\n result = Promise.resolve(this._performRequest(options))\n // Note: we cache the normalized (parsed) response\n .then((text) => this._normalizeResponse(text, options));\n this._cache.add(cache_key, result, options._cache_meta);\n // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request\n // Otherwise, temporary failures couldn't be resolved by trying again in a moment\n // TODO: In the future, consider providing a way to skip requests (eg, a sentinel value to flag something\n // as not cacheable, like \"no dependent data means no request... but maybe in another place this is used, there will be different dependent data and a request would make sense\")\n result.catch((e) => this._cache.remove(cache_key));\n }\n\n return result\n // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache\n .then((data) => clone(data))\n .then((records) => this._annotateRecords(records, options))\n .then((records) => this._postProcessResponse(records, options));\n }\n}\n\n\n/**\n * Fetch data over the web, usually from a REST API that returns JSON\n * @param {string} config.url The URL to request\n * @extends module:undercomplicate.BaseAdapter\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseUrlAdapter extends BaseAdapter {\n constructor(config = {}) {\n super(config);\n this._url = config.url;\n }\n\n\n /**\n * Default cache key is the URL for this request\n * @public\n */\n _getCacheKey(options) {\n return this._getURL(options);\n }\n\n /**\n * In many cases, the base url should be modified with query parameters based on request options.\n * @param options\n * @returns {*}\n * @private\n */\n _getURL(options) {\n return this._url;\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.\n // (not validating in constructor allows URL adapter to be used as more generic parent class)\n if (!this._url) {\n throw new Error('Web based resources must specify a resource URL as option \"url\"');\n }\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n });\n }\n\n _normalizeResponse(response_text, options) {\n if (typeof response_text === 'string') {\n return JSON.parse(response_text);\n }\n // Some custom usages will return other datatypes. These would need to be handled by custom normalization logic in a subclass.\n return response_text;\n }\n}\n\nexport { BaseAdapter, BaseUrlAdapter };\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value,\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import TRANSFORMS from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n // Two scenarios: we are requesting a field by full name, OR there are transforms to apply\n // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`\n const field_pattern = /^(?:\\w+:\\w+|^\\w+)(?:\\|\\w+)*$/;\n if (!field_pattern.test(field)) {\n throw new Error(`Invalid field specifier: '${field}'`);\n }\n\n const [name, ...transforms] = field.split('|');\n\n this.full_name = field; // fieldname + transforms\n this.field_name = name; // just fieldname\n this.transformations = transforms.map((name) => TRANSFORMS.get(name));\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (data[this.field_name] !== undefined) { // Fallback: value sans transforms\n val = data[this.field_name];\n } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations\n val = extra[this.field_name];\n } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {},\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable,\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * @module\n * @private\n */\nimport {getLinkedData} from './undercomplicate';\n\nimport { DATA_OPS } from '../registry';\n\n\nclass DataOperation {\n /**\n * Perform a data operation (such as a join)\n * @param {String} join_type\n * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!\n * @param params Optional user/layout parameters to be passed to the data function\n */\n constructor(join_type, initiator, params) {\n this._callable = DATA_OPS.get(join_type);\n this._initiator = initiator;\n this._params = params || [];\n }\n\n getData(plot_state, ...dependent_recordsets) {\n // Most operations are joins: they receive two pieces of data (eg left + right)\n // Other ops are possible, like consolidating just one set of records to best value per key\n // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]\n\n // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options\n const context = {plot_state, data_layer: this._initiator};\n return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));\n }\n}\n\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a\n * designated order.\n *\n * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can\n * perform its own calculations, filter results, and apply transforms without influencing other layers.\n * (while still respecting a shared cache where appropriate)\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n /**\n * Parse the data layer configuration when a layer is first created.\n * Validate config, and return entities and dependencies in a format usable for data retrieval.\n * This is used by data layers, and also other data-retrieval functions (like subscribeToDate).\n *\n * Inherent assumptions:\n * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed.\n * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be\n * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the\n * removed adapter.\n * @param {Object} namespace_options\n * @param {Array} data_operations\n * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations,\n * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate\n * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care!\n * @returns {Array} Map of entities and list of dependencies\n */\n config_to_sources(namespace_options = {}, data_operations = [], initiator) {\n const entities = new Map();\n const namespace_local_names = Object.keys(namespace_options);\n\n // 1. Specify how to coordinate data. Precedence:\n // a) EXPLICIT fetch logic,\n // b) IMPLICIT auto-generate fetch order if there is only one NS,\n // c) Throw \"spec required\" error if > 1, because 2 adapters may need to be fetched in a sequence\n let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from}\n if (!dependency_order) {\n dependency_order = { type: 'fetch', from: namespace_local_names };\n data_operations.unshift(dependency_order);\n }\n\n // Validate that all NS items are available to the root requester in DataSources. All layers recognize a\n // default value, eg people copying the examples tend to have defined a datasource called \"assoc\"\n const ns_pattern = /^\\w+$/;\n for (let [local_name, global_name] of Object.entries(namespace_options)) {\n if (!ns_pattern.test(local_name)) {\n throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`);\n }\n\n const source = this._sources.get(global_name);\n if (!source) {\n throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`);\n }\n entities.set(local_name, source);\n\n // Note: Dependency spec checker will consider \"ld(assoc)\" to match a namespace called \"ld\"\n if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) {\n // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join.\n // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch\n // Thus the default behavior is \"fetch all namespaces as though they don't depend on anything.\n // If they depend on something, only then does \"data_ops[@type=fetch].from\" need to be mutated\n dependency_order.from.push(local_name);\n }\n }\n\n let dependencies = Array.from(dependency_order.from);\n\n // Now check all joins. Are namespaces valid? Are they requesting known data?\n for (let config of data_operations) {\n let {type, name, requires, params} = config;\n if (type !== 'fetch') {\n let namecount = 0;\n if (!name) {\n name = config.name = `join${namecount}`;\n namecount += 1;\n }\n\n if (entities.has(name)) {\n throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`);\n }\n requires.forEach((require_name) => {\n if (!entities.has(require_name)) {\n throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`);\n }\n });\n\n const task = new DataOperation(type, initiator, params);\n entities.set(name, task);\n dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB)\n }\n }\n return [entities, dependencies];\n }\n\n /**\n * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end)\n * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts.\n * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances\n * (things that implement a method getData).\n * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order\n * @returns {Promise}\n */\n getData(plot_state, entities, dependencies) {\n if (!dependencies.length) {\n return Promise.resolve([]);\n }\n // The last dependency (usually the last join operation) determines the last thing returned.\n return getLinkedData(plot_state, entities, dependencies, true);\n }\n}\n\n\nexport default Requester;\n\nexport {DataOperation as _JoinTask};\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay,\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import WIDGETS from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n const options = this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n this.addWidget(layout);\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown)\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Add a new widget to the toolbar.\n * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to:\n * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here.\n * - call widget.show() to ensure that the widget is initialized and rendered correctly\n * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default.\n * @param {Object} layout The layout object describing the desired widget\n * @returns {layout.type}\n */\n addWidget(layout) {\n try {\n const widget = WIDGETS.create(layout.type, layout, this);\n this.widgets.push(widget);\n return widget;\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 14,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n const layer_legend = this.parent.data_layers[id].layout.legend;\n if (Array.isArray(layer_legend)) {\n layer_legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape === 'ribbon') {\n // Color ribbons describe a series of color stops: small boxes of color across a continuous\n // scale. Drawn horizontally, or vertically, like:\n // [red | orange | yellow | green ] label\n // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way.\n const width = +element.width || 25;\n const height = +element.height || width;\n const is_horizontal = (element.orientation || 'vertical') === 'horizontal';\n let color_stops = element.color_stops;\n\n const all_elements = selector.append('g');\n const ribbon_group = all_elements.append('g');\n const axis_group = all_elements.append('g');\n let axis_offset = 0;\n if (element.tick_labels) {\n let range;\n if (is_horizontal) {\n range = [0, width * color_stops.length - 1]; // 1 px offset to align tick with inner borders\n } else {\n range = [height * color_stops.length - 1, 0];\n }\n const scale = d3.scaleLinear()\n .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode\n .range(range);\n const axis = (is_horizontal ? d3.axisTop : d3.axisRight)(scale)\n .tickSize(3)\n .tickValues(element.tick_labels)\n .tickFormat((v) => v);\n axis_group\n .call(axis)\n .attr('class', 'lz-axis');\n let bcr = axis_group.node().getBoundingClientRect();\n axis_offset = bcr.height;\n }\n if (is_horizontal) {\n // Shift axis down (so that tick marks aren't above the origin)\n axis_group\n .attr('transform', `translate(0, ${axis_offset})`);\n // Ribbon appears below axis\n ribbon_group\n .attr('transform', `translate(0, ${axis_offset})`);\n } else {\n // Vertical mode: Shift axis ticks to the right of the ribbon\n all_elements.attr('transform', 'translate(5, 0)');\n axis_group\n .attr('transform', `translate(${width}, 0)`);\n }\n\n if (!is_horizontal) {\n // Vertical mode: renders top -> bottom but scale is usually specified low..high\n color_stops = color_stops.slice();\n color_stops.reverse();\n }\n for (let i = 0; i < color_stops.length; i++) {\n const color = color_stops[i];\n const to_next_marking = is_horizontal ? `translate(${width * i}, 0)` : `translate(0, ${height * i})`;\n ribbon_group\n .append('rect')\n .attr('class', element.class || '')\n .attr('stroke', 'black')\n .attr('transform', to_next_marking)\n .attr('stroke-width', 0.5)\n .attr('width', width)\n .attr('height', height)\n .attr('fill', color)\n .call(applyStyles, element.style || {});\n }\n\n // Note: In vertical mode, it's usually easier to put the label above the legend as a separate marker\n // This is because the legend element label is drawn last (can't use it's size to position the ribbon, which is drawn first)\n if (!is_horizontal && element.label) {\n throw new Error('Legend labels not supported for vertical ribbons (use a separate legend item as text instead)');\n }\n // This only makes sense for horizontal labels.\n label_x = (width * color_stops.length + padding);\n label_y += axis_offset;\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset] The distance between the axis title and the axis. Use this to prevent\n * the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly.\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n if (typeof layout.id !== 'string' || !layout.id) {\n throw new Error('Panel layouts must specify \"id\"');\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this._layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this._state_id = this.id;\n this.state[this._state_id] = this.state[this._state_id] || {};\n } else {\n this.state = null;\n this._state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this._data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this._data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this._zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this._event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this._event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this._data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this._data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id]._layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n const target_layer = this.data_layers[id];\n if (!target_layer) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n target_layer.destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (target_layer.svg.container) {\n target_layer.svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(target_layer._layout_idx, 1);\n delete this.state[target_layer._state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id]._layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n const { cliparea } = this.layout;\n\n // Set and position the inner border, style if necessary\n const { margin } = this.layout;\n this.inner_border\n .attr('x', margin.left)\n .attr('y', margin.top)\n .attr('width', this.parent_plot.layout.width - (margin.left + margin.right))\n .attr('height', this.layout.height - (margin.top + margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n const axes_config = this.layout.axes;\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (axes_config.x.range) {\n base_x_range.start = axes_config.x.range.start || base_x_range.start;\n base_x_range.end = axes_config.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: cliparea.height, end: 0 };\n if (axes_config.y1.range) {\n base_y1_range.start = axes_config.y1.range.start || base_y1_range.start;\n base_y1_range.end = axes_config.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: cliparea.height, end: 0 };\n if (axes_config.y2.range) {\n base_y2_range.start = axes_config.y2.range.start || base_y2_range.start;\n base_y2_range.end = axes_config.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n let { _interaction } = this.parent;\n const current_drag = _interaction.dragging;\n if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (_interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = _interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = _interaction.zooming.center - margin.left - this.layout.origin.x;\n const offset_ratio = anchor / cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (current_drag) {\n switch (current_drag.method) {\n case 'background':\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n } else {\n anchor = current_drag.start_x - margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${current_drag.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y;\n ranges[y_shifted][1] = +current_drag.dragged_y;\n } else {\n anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3);\n ranges[y_shifted][0] = cliparea.height;\n ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent._interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n // Redefine b/c might have been changed during call to parent re-render\n _interaction = this.parent._interaction;\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this._zoom_timeout !== null) {\n clearTimeout(this._zoom_timeout);\n }\n this._zoom_timeout = setTimeout(() => {\n this.parent._interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this._initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((id) => {\n const axis = this.layout.axes[id];\n if (!Object.keys(axis).length || axis.render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n axis.render = false;\n } else {\n axis.render = true;\n axis.label = axis.label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n const layout = this.layout;\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n layout.height = Math.max(Math.round(+height), layout.min_height);\n }\n }\n layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0);\n layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', layout.height);\n }\n if (this._initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n const { cliparea, margin } = this.layout;\n if (!isNaN(top) && top >= 0) {\n margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (margin.top + margin.bottom > this.layout.height) {\n extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2);\n margin.top -= extra;\n margin.bottom -= extra;\n }\n if (margin.left + margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2);\n margin.left -= extra;\n margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n margin[m] = Math.max(margin[m], 0);\n });\n cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0);\n cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0);\n cliparea.origin.x = margin.left;\n cliparea.origin.y = margin.top;\n\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this._data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent._panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n const { parent } = this;\n const y_index = this.layout.y_index;\n if (parent._panel_ids_by_y_index[y_index - 1]) {\n parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1];\n parent._panel_ids_by_y_index[y_index - 1] = this.id;\n parent.applyPanelYIndexesToPanelLayouts();\n parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n const { _panel_ids_by_y_index } = this.parent;\n if (_panel_ids_by_y_index[this.layout.y_index + 1]) {\n _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1];\n _panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this._data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this._data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this._data_promises)\n .then(() => {\n this._initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([\\w+_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future.\n * @event data_from_layer\n * @property {object} data\n * @property {String} data.layer The fully qualified ID of the layer emitting this event\n * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum\n * element. It reflects all namespaces and data operations used by that layer.\n * @see event:any_lz_event\n */\n\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this._initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this._panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this._remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this._interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this._event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this._event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this._panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this._panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id]._layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer._layer_state;\n delete this.layout.state[layer._state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n const target_panel = this.panels[id];\n if (!target_panel) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this._panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n target_panel.loader.hide();\n target_panel.toolbar.destroy(true);\n target_panel.curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (target_panel.svg.container) {\n target_panel.svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(target_panel._layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id]._layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the\n * structure of the data, instead of just displaying something.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @listens event:data_from_layer\n * @param {Object} [opts] Options\n * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched.\n * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details.\n * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested,\n * this is usually required in order to specify dependency order and join operations. See data layer documentation for details.\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(opts, success_callback) {\n const { from_layer, namespace, data_operations, onerror } = opts;\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = onerror || function (err) {\n console.error('An error occurred while acting on an external callback', err);\n };\n\n if (from_layer) {\n // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places.\n const base_prefix = `${this.getBaseId()}.`;\n // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code.\n const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`;\n\n // Ensure that a valid layer exists to watch\n let is_valid_layer = false;\n for (let p of Object.values(this.panels)) {\n is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target);\n if (is_valid_layer) {\n break;\n }\n }\n if (!is_valid_layer) {\n throw new Error(`Could not subscribe to unknown data layer ${layer_target}`);\n }\n\n const listener = (eventData) => {\n if (eventData.data.layer !== layer_target) {\n // Same event name fires for many layers; only fire success cb for the one layer we want\n return;\n }\n try {\n success_callback(eventData.data.content, this);\n } catch (error) {\n error_callback(error);\n }\n };\n\n this.on('data_from_layer', listener);\n return listener;\n }\n\n // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of\n // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized\n // in connection to some other visualization)\n if (!namespace) {\n throw new Error(\"subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option\");\n }\n\n const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot.\n const listener = () => {\n try {\n // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely,\n // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame.\n this.lzd.getData(this.state, entities, dependencies)\n .then((new_data) => success_callback(new_data, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this._remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this._remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this._remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this._initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /**\n * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another,\n * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties,\n * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.\n * @public\n */\n mutateLayout() {\n Object.values(this.panels).forEach((panel) => {\n Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout());\n });\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n const { _interaction } = this;\n if (panel_id) {\n return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(_interaction.dragging || _interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this._panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this._initialized) {\n this._panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let panel of Object.values(this.panels)) {\n if (panel.layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_layout = panel.layout;\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel_layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel_layout.margin.right, 0);\n panel_layout.width += delta;\n panel_layout.margin.left = x_linked_margins.left;\n panel_layout.margin.right = x_linked_margins.right;\n panel_layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height,\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n const resize_listener = () => window.requestAnimationFrame(() => this.rescaleSVG());\n\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener\n // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed\n if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers\n const options = { root: document.documentElement, threshold: 0.9 };\n const observer = new IntersectionObserver((entries, observer) => {\n if (entries.some((entry) => entry.intersectionRatio > 0)) {\n this.rescaleSVG();\n }\n }, options);\n // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup\n observer.observe(this.container);\n }\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this._mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this._panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent._panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent._panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this._panel_boundaries.hide_timeout);\n this._panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this._panel_boundaries.hide_timeout = setTimeout(() => {\n this._panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this._mouse_guide.vertical.attr('x', -1);\n this._mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this._mouse_guide.vertical.attr('x', coords[0]);\n this._mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n const { _interaction } = this;\n if (_interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x;\n _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y;\n this.panels[_interaction.panel_id].render();\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this._initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this._interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n const { _interaction } = this;\n if (!_interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[_interaction.panel_id] != 'object') {\n this._interaction = {};\n return this;\n }\n const panel = this.panels[_interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel._data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (_interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (_interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (_interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(_interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this._interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} value value\n */\nconst if_value = (parameters, value) => {\n if (typeof value == 'undefined' || parameters.field_value !== value) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} value value\n * @returns {*}\n */\nconst numerical_bin = (parameters, value) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+value < prev || (+value >= prev && +value < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @function ordinal_cycle\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n *\n * @function stable_choice\n *\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, value) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return nullval;\n }\n if (+value <= parameters.breaks[0]) {\n return values[0];\n } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +value && breaks[idx] >= +value) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\n/**\n * Calculate the effect direction based on beta, or the combination of beta and standard error.\n * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.\n *\n * @function effect_direction\n * @param parameters\n * @param parameters.'+' The value to return if the effect direction is positive\n * @param parameters.'-' The value to return if the effect direction is positive\n * @param parameters.beta_field The name of the field containing beta\n * @param parameters.stderr_beta_field The name of the field containing stderr_beta\n * @param {Object} input This function should receive the entire datum object, rather than one single field\n * @returns {null}\n */\nfunction effect_direction(parameters, input) {\n if (input === undefined) {\n return null;\n }\n\n const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters;\n\n if (!beta_field || !stderr_beta_field) {\n throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`);\n }\n\n const beta_val = input[beta_field];\n const se_val = input[stderr_beta_field];\n\n if (beta_val !== undefined) {\n if (se_val !== undefined) {\n if ((beta_val - 1.96 * se_val) > 0) {\n return plus_result;\n } else if ((beta_val + 1.96 * se_val) < 0) {\n return neg_result || null;\n }\n } else {\n if (beta_val > 0) {\n return plus_result;\n } else if (beta_val < 0) {\n return neg_result;\n }\n }\n }\n // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid\n // about expected data formats for layouts.\n return null;\n}\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, findFields, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n/**\n * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.\n * @property {module:LocusZoom_DataFunctions|'fetch'} type\n * @property {String[]} [from] For operations of type \"fetch\", this is required. By default, it will fill in any items provided in \"namespace\" (everything specified in namespace triggers an adapter/network request)\n * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace).\n * Eg, for ld to be fetched after association data, specify \"ld(assoc)\". Most LocusZoom examples fill in all items, in order to make the examples more clear.\n * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation.\n * Eg, if the retrieved data is combined via several left joins in series: `name: \"assoc_plus_ld\"` -> feeds into `{name: \"final\", requires: [\"assoc_plus_ld\"]}`\n * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just\n * the names of other namespaces (or data operations) whose results must be available before a join can be performed\n * @property {String[]} params Any user-defined parameters that should be passed to the particular join function\n * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']`\n * Separate from this section, data functions will also receive a copy of \"plot.state\" automatically.\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or \"line\" for a path.\n * A special \"ribbon\" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD)\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {'vertical'|'horizontal'} [orientation='vertical'] For shape \"ribbon\", specifies whether to draw the ribbon vertically or horizontally.\n * @property {Array} [tick_labels] For shape \"ribbon\", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops.\n * @property {String[]} [color_stops] For shape \"ribbon\", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels.\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n namespace: {},\n data_operations: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is\n * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely\n * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is\n * your job to assure that all of the expected fields are present in every element)\n * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data (\"assoc\")\n * to the globally unique name for something defined in LocusZoom.DataSources.\n * Namespaces allow a single layout to be reused to plot many tracks of the same type: \"given some form of association data, plot it\".\n * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse\n * a layout with a new provider of data- like plotting two association studies stacked together-\n * only the namespace section of the layout needs to be overridden.\n * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})`\n * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions})\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this._layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this._state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this._layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this._tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this._global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n\n // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval\n this._data_contract = new Set(); // List of all fields requested by the layout\n this._entities = new Map();\n this._dependencies = [];\n this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout\n }\n\n /****** Public interface: methods for manipulating the layer from other parts of LZ */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index + 1]) {\n layer_order[current_index] = layer_order[current_index + 1];\n layer_order[current_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index - 1]) {\n layer_order[current_index] = layer_order[current_index - 1];\n layer_order[current_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this._layer_state.extra_fields[id]) {\n this._layer_state.extra_fields[id] = {};\n }\n this._layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /**\n * A list of operations that should be run when the layout is mutated\n * Typically, these are things done once when a layout is first specified, that would not automatically\n * update when the layout was changed.\n * @public\n */\n mutateLayout() {\n // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract.\n if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester\n const { namespace, data_operations } = this.layout;\n this._data_contract = findFields(this.layout, Object.keys(namespace));\n const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this);\n this._entities = entities;\n this._dependencies = dependencies;\n }\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n *\n * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that\n * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data),\n * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}`\n * @protected\n * @param {Object} element The data associated with a particular element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression\n const id_field = this.layout.id_field;\n let value = element[id_field];\n if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) {\n // No field value was found directly, but if it looks like a template expression, next, try parsing that\n // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot!\n value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient\n }\n if (value === null || value === undefined) {\n // Neither exact field nor template options produced an ID\n throw new Error('Unable to generate element ID');\n }\n const element_id = value.toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Abstract method. It should be overridden by data layers that implement separate status\n * nodes, such as genes or intervals.\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or\n * a group of unrelated intervals (all markings grouped within a category).\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n\n // Does the data from the API satisfy the list of fields expected by this layout?\n // Not every record will have every possible field (example: left joins like assoc + ld). The check is \"did\n // we see this field at least once in any record at all\".\n if (this.data.length && this._data_contract.size) {\n const fields_unseen = new Set(this._data_contract);\n for (let record of this.data) {\n Object.keys(record).forEach((field) => fields_unseen.delete(field));\n if (!fields_unseen.size) {\n // Once every requested field has been seen in at least one record, no need to look at more records\n break;\n }\n }\n if (fields_unseen.size) {\n // Current implementation is a soft warning, so that certain \"incremental enhancement\" features\n // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info.\n // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data.\n console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in \"data_operations\" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`);\n }\n }\n\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value);\n }\n\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this._layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const _layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = _layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this._state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this._state_id] = _layer_state;\n }\n this._layer_state = _layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this._tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this._tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this._layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this._tooltips[id].selector.html('');\n this._tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this._tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this._tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this._tooltips[id]) {\n if (typeof this._tooltips[id].selector == 'object') {\n this._tooltips[id].selector.remove();\n }\n delete this._tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this._layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this._tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this._tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this._tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this._tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n const tooltip_layout = this.layout.tooltip;\n if (typeof tooltip_layout != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof tooltip_layout.show == 'string') {\n show_directive = { and: [ tooltip_layout.show ] };\n } else if (typeof tooltip_layout.show == 'object') {\n show_directive = tooltip_layout.show;\n }\n\n let hide_directive = {};\n if (typeof tooltip_layout.hide == 'string') {\n hide_directive = { and: [ tooltip_layout.hide ] };\n } else if (typeof tooltip_layout.hide == 'object') {\n hide_directive = tooltip_layout.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const _layer_state = this._layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (_layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this._layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this._layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true,\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this._layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this._layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self._state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies)\n .then((new_data) => {\n this.data = new_data;\n this.applyDataMethods();\n this._initialized = true;\n // Allow listeners (like subscribeToData) to see the information associated with a layer\n this.parent.emit(\n 'data_from_layer',\n { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin\n true,\n );\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 15,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n // FIXME: Make gene text font sizes scalable\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size,\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n tooltip: null,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this._global_statuses).forEach((global_status) => {\n if (this._global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n data_layer._label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this._label_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer._label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer._label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer._label_texts.nodes();\n data_layer._label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this._label_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this._label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this._label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this._label_texts) {\n this._label_texts.remove();\n }\n\n this._label_texts = this._label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this._label_lines) {\n this._label_lines.remove();\n }\n this._label_lines = this._label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this._label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this._label_texts) {\n this._label_texts.remove();\n }\n if (this._label_lines) {\n this._label_lines.remove();\n }\n if (this._label_groups) {\n this._label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this._label_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{assoc:variant|htmlescape}}
          \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
          \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
          \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
          `,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

          {{gene_name|htmlescape}}

          '\n + 'Gene ID: {{gene_id|htmlescape}}
          '\n + 'Transcript ID: {{transcript_id|htmlescape}}
          '\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
          ConstraintExpected variantsObserved variantsConst. Metric
          Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
          o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
          Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
          o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
          pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
          o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

          {{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{catalog:variant|htmlescape}}
          '\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
          '\n + 'Top Trait: {{catalog:trait|htmlescape}}
          '\n + 'Top P Value: {{catalog:log_pvalue|logtoscinotation}}
          '\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
          ' +\n '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
          ' +\n 'Promoter
          ' +\n '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
          ' +\n '{{#if access:target}}Target: {{access:target|htmlescape}}
          {{/if}}' +\n 'Score: {{access:score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n id: 'recombrate',\n namespace: { 'recomb': 'recomb' },\n data_operations: [\n { type: 'fetch', from: ['recomb'] },\n ],\n type: 'line',\n tag: 'recombination',\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: 'recomb:position',\n },\n y_axis: {\n axis: 2,\n field: 'recomb:recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)'],\n },\n {\n type: 'left_match',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'ld'],\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n id_field: 'assoc:variant',\n coalesce: {\n active: true,\n },\n point_shape: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 'diamond',\n },\n },\n {\n // Not every dataset will provide these params\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'assoc:beta',\n stderr_beta_field: 'assoc:se',\n },\n },\n 'circle',\n ],\n point_size: {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: 'ld:correlation',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8'\n {\n shape: 'ribbon',\n orientation: 'vertical',\n width: 10,\n height: 15,\n color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0],\n },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: 'assoc:position',\n },\n y_axis: {\n axis: 1,\n field: 'assoc:log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n namespace: { 'access': 'access' },\n data_operations: [\n { type: 'fetch', from: ['access'] },\n ],\n match: { send: 'access:target', receive: 'access:target' },\n // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.\n id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',\n filters: [\n { field: 'access:score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: 'access:start1',\n field2: 'access:start2',\n },\n y_axis: {\n axis: 1,\n field: 'access:score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n\n base.data_operations.push({\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_catalog',\n requires: ['assoc_plus_ld', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n });\n\n base.tooltip.html += '{{#if catalog:rsid}}
          See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n return base;\n}();\n\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n namespace: { 'phewas': 'phewas' },\n data_operations: [\n { type: 'fetch', from: ['phewas'] },\n ],\n point_shape: [\n {\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'phewas:beta',\n stderr_beta_field: 'phewas:se',\n },\n },\n 'circle',\n ],\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',\n x_axis: {\n field: 'lz_auto_x', // Automatically added by the category_scatter layer\n category_field: 'phewas:trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: 'phewas:log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: 'phewas:trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Trait: {{phewas:trait_label|htmlescape}}
          \nTrait Category: {{phewas:trait_group|htmlescape}}
          \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
          β: {{phewas:beta|scinotation|htmlescape}}
          {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}`,\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{phewas:trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: 'phewas:log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n data_operations: [\n {\n type: 'fetch',\n from: ['gene', 'constraint(gene)'],\n },\n {\n name: 'gene_constraint',\n type: 'genes_to_gnomad_constraint',\n requires: ['gene', 'constraint'],\n },\n ],\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n data_operations: [\n {\n type: 'fetch', from: ['assoc', 'catalog'],\n },\n {\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n },\n ],\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: '#0000CC',\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'catalog:rsid', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n },\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 300,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 46,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 75, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 40,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{catalog:trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: 'catalog:trait', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: 'ld:correlation', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '12px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 55, bottom: 20, left: 70 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu),\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 55, bottom: 120, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 55, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel),\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or applying namespaces.\n let base = super.get(type).get(name);\n\n // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.\n // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)\n const custom_namespaces = overrides.namespace;\n if (!base.namespace) {\n // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout\n // NOTE: The \"merge namespace\" behavior means that data layers can add new data easily, but this method\n // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).\n delete overrides.namespace;\n }\n let result = merge(overrides, base);\n\n if (custom_namespaces) {\n result = applyNamespaces(result, custom_namespaces);\n }\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n\n // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested\n // from external sources. This is purely a hint, because not every layout is generated through the registry.\n if (type === 'data_layer' && copy.namespace) {\n copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();\n }\n\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","/**\n * \"Data operation\" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results\n *\n * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation\n * is a \"join\", such as combining association + LD together into a single set of records for plotting. Several join\n * functions (that operate by analogy to SQL) are provided built-in.\n *\n * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):\n * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.\n * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,\n * this is the recommended path to do so)\n * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),\n * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,\n * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks\n * (PheWAS), automatic panel legends or color schemes (BED tracks), etc.\n *\n * Usually, a data operation receives two recordsets (the left and right members of the join, like \"assoc\" and \"ld\").\n * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network\n * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is\n * uncommon. (if possible, try to provide your data with fewer adapters/network requests!)\n *\n * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,\n * particularly for advanced features, may carry assumptions about field names/ formatting.\n * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,\n * or it may match two datasets based on a specific way of identifying the variant)\n *\n * @module LocusZoom_DataFunctions\n */\nimport {joins} from '../data/undercomplicate';\n\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"data join\" functions.\n * @alias module:LocusZoom~DataFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\nfunction _wrap_join(handle) {\n // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).\n\n // Many of our join functions are implemented with a different number of arguments than what a datafunction\n // actually receives. (eg, a join function is generic and doesn't care about \"context\" information like plot.state)\n // This wrapper is simple shared code to handle required validation and conversion stuff.\n return (context, deps, ...params) => {\n if (deps.length !== 2) {\n throw new Error('Join functions must receive exactly two recordsets');\n }\n return handle(...deps, ...params);\n };\n}\n\n// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to\n// pick the most significant claim in the catalog for a variant, rather than joining every possible match.\n// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.\nfunction assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {\n if (!assoc_data.length) {\n return assoc_data;\n }\n\n // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each\n const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);\n\n const catalog_flat = []; // Store only the top significant claim for each catalog variant entry\n for (let claims of catalog_by_variant.values()) {\n // Find max item within this set of claims, push that to catalog_\n let best = 0;\n let best_variant;\n for (let item of claims) {\n const val = item[catalog_logp_name];\n if ( val >= best) {\n best_variant = item;\n best = val;\n }\n }\n best_variant.n_catalog_matches = claims.length;\n catalog_flat.push(best_variant);\n }\n return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);\n}\n\n// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.\nfunction genes_to_gnomad_constraint(genes_data, constraint_data) {\n genes_data.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return genes_data;\n}\n\n\n/**\n * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset\n *\n * @function\n * @name left_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('left_match', _wrap_join(joins.left_match));\n\n/**\n * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.\n *\n * @function\n * @name inner_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('inner_match', _wrap_join(joins.inner_match));\n\n/**\n * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.\n *\n * @function\n * @name full_outer_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('full_outer_match', _wrap_join(joins.full_outer_match));\n\n/**\n * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.\n *\n * @function\n * @name assoc_to_gwas_catalog\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: assoc records, then catalog records\n * @param {String} assoc_key The name of the key field in association data, eg variant ID\n * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID\n * @param {String} catalog_log_p_name The name of the \"log_pvalue\" field in gwas catalog data, used to choose the most significant claim for a given variant\n */\nregistry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));\n\n/**\n * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).\n *\n * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.\n *\n * @function\n * @name genes_to_gnomad_constraint\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data\n */\nregistry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));\n\nexport default registry;\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n DATA_OPS as DataFunctions,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n WIDGETS as Widgets,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n DataFunctions,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/docs/api/LLNode.html b/docs/api/LLNode.html index dd958dd9..38d9d43d 100644 --- a/docs/api/LLNode.html +++ b/docs/api/LLNode.html @@ -333,7 +333,7 @@

          Home

          Modules

          • diff --git a/docs/api/LayoutRegistry.html b/docs/api/LayoutRegistry.html index ce6d26dc..e58b9eb1 100644 --- a/docs/api/LayoutRegistry.html +++ b/docs/api/LayoutRegistry.html @@ -1430,7 +1430,7 @@

            Home

            Modules

            • diff --git a/docs/api/Line.html b/docs/api/Line.html index 135d0080..35601068 100644 --- a/docs/api/Line.html +++ b/docs/api/Line.html @@ -725,7 +725,7 @@

              Home

              Modules

              • diff --git a/docs/api/Panel.html b/docs/api/Panel.html index f5dd8596..adb7109b 100644 --- a/docs/api/Panel.html +++ b/docs/api/Panel.html @@ -4398,7 +4398,7 @@

                Home

                Modules

                • diff --git a/docs/api/Plot.html b/docs/api/Plot.html index 4f19707b..cfa6bcb1 100644 --- a/docs/api/Plot.html +++ b/docs/api/Plot.html @@ -3035,7 +3035,7 @@

                  Home

                  Modules

                  • diff --git a/docs/api/TransformationFunctionsRegistry.html b/docs/api/TransformationFunctionsRegistry.html index c2fe0b25..8b72ea7c 100644 --- a/docs/api/TransformationFunctionsRegistry.html +++ b/docs/api/TransformationFunctionsRegistry.html @@ -311,7 +311,7 @@

                    Home

                    Modules

                    • diff --git a/docs/api/components_data_layer_annotation_track.js.html b/docs/api/components_data_layer_annotation_track.js.html index 6947cbb0..68bbd6b4 100644 --- a/docs/api/components_data_layer_annotation_track.js.html +++ b/docs/api/components_data_layer_annotation_track.js.html @@ -177,7 +177,7 @@

                      Home

                      Modules

                      • diff --git a/docs/api/components_data_layer_arcs.js.html b/docs/api/components_data_layer_arcs.js.html index c9c9db31..58640ad4 100644 --- a/docs/api/components_data_layer_arcs.js.html +++ b/docs/api/components_data_layer_arcs.js.html @@ -186,7 +186,7 @@

                        Home

                        Modules

                        • diff --git a/docs/api/components_data_layer_base.js.html b/docs/api/components_data_layer_base.js.html index 0ac920bc..d438b376 100644 --- a/docs/api/components_data_layer_base.js.html +++ b/docs/api/components_data_layer_base.js.html @@ -1720,7 +1720,7 @@

                          Home

                          Modules

                          • diff --git a/docs/api/components_data_layer_genes.js.html b/docs/api/components_data_layer_genes.js.html index 4e16479c..200ec32c 100644 --- a/docs/api/components_data_layer_genes.js.html +++ b/docs/api/components_data_layer_genes.js.html @@ -431,7 +431,7 @@

                            Home

                            Modules

                            • diff --git a/docs/api/components_data_layer_highlight_regions.js.html b/docs/api/components_data_layer_highlight_regions.js.html index f03d81f1..8fe5ae48 100644 --- a/docs/api/components_data_layer_highlight_regions.js.html +++ b/docs/api/components_data_layer_highlight_regions.js.html @@ -183,7 +183,7 @@

                              Home

                              Modules

                              • diff --git a/docs/api/components_data_layer_line.js.html b/docs/api/components_data_layer_line.js.html index 5e80768c..447da755 100644 --- a/docs/api/components_data_layer_line.js.html +++ b/docs/api/components_data_layer_line.js.html @@ -309,7 +309,7 @@

                                Home

                                Modules

                                • diff --git a/docs/api/components_data_layer_scatter.js.html b/docs/api/components_data_layer_scatter.js.html index fff43a5c..c93c0744 100644 --- a/docs/api/components_data_layer_scatter.js.html +++ b/docs/api/components_data_layer_scatter.js.html @@ -722,7 +722,7 @@

                                  Home

                                  Modules

                                  • diff --git a/docs/api/components_legend.js.html b/docs/api/components_legend.js.html index c410566c..8273f57e 100644 --- a/docs/api/components_legend.js.html +++ b/docs/api/components_legend.js.html @@ -359,7 +359,7 @@

                                    Home

                                    Modules

                                    • diff --git a/docs/api/components_panel.js.html b/docs/api/components_panel.js.html index e0d3cd4d..ac06eb44 100644 --- a/docs/api/components_panel.js.html +++ b/docs/api/components_panel.js.html @@ -1611,7 +1611,7 @@

                                      Home

                                      Modules

                                      • diff --git a/docs/api/components_plot.js.html b/docs/api/components_plot.js.html index 419c495f..530de606 100644 --- a/docs/api/components_plot.js.html +++ b/docs/api/components_plot.js.html @@ -1144,7 +1144,8 @@

                                        Source: components/plot.js

                                        d3.select(this.container).classed('lz-container-responsive', true); // If this is a responsive layout then set a namespaced/unique onresize event listener on the window - const resize_listener = () => this.rescaleSVG(); + const resize_listener = () => window.requestAnimationFrame(() => this.rescaleSVG()); + window.addEventListener('resize', resize_listener); this.trackExternalListener(window, 'resize', resize_listener); @@ -1549,7 +1550,7 @@

                                        Home

                                        Modules

                                        • diff --git a/docs/api/components_toolbar_index.js.html b/docs/api/components_toolbar_index.js.html index 2bb5ea07..47e2d609 100644 --- a/docs/api/components_toolbar_index.js.html +++ b/docs/api/components_toolbar_index.js.html @@ -277,7 +277,7 @@

                                          Home

                                          Modules

                                          • diff --git a/docs/api/components_toolbar_widgets.js.html b/docs/api/components_toolbar_widgets.js.html index 185e3b36..0721b337 100644 --- a/docs/api/components_toolbar_widgets.js.html +++ b/docs/api/components_toolbar_widgets.js.html @@ -1677,7 +1677,7 @@

                                            Home

                                            Modules

                                            • diff --git a/docs/api/data_adapters.js.html b/docs/api/data_adapters.js.html index bfa61063..38d9593e 100644 --- a/docs/api/data_adapters.js.html +++ b/docs/api/data_adapters.js.html @@ -783,7 +783,7 @@

                                              Home

                                              Modules

                                              • diff --git a/docs/api/data_field.js.html b/docs/api/data_field.js.html index f371791d..d1c18875 100644 --- a/docs/api/data_field.js.html +++ b/docs/api/data_field.js.html @@ -105,7 +105,7 @@

                                                Home

                                                Modules

                                                • diff --git a/docs/api/data_requester.js.html b/docs/api/data_requester.js.html index 38d9208c..3d0fb720 100644 --- a/docs/api/data_requester.js.html +++ b/docs/api/data_requester.js.html @@ -200,7 +200,7 @@

                                                  Home

                                                  Modules

                                                  • diff --git a/docs/api/data_sources.js.html b/docs/api/data_sources.js.html index b1b827f9..5e56cf57 100644 --- a/docs/api/data_sources.js.html +++ b/docs/api/data_sources.js.html @@ -98,7 +98,7 @@

                                                    Home

                                                    Modules

                                                    • diff --git a/docs/api/data_undercomplicate_adapter.js.html b/docs/api/data_undercomplicate_adapter.js.html index 21d3513a..da38e5e7 100644 --- a/docs/api/data_undercomplicate_adapter.js.html +++ b/docs/api/data_undercomplicate_adapter.js.html @@ -247,7 +247,7 @@

                                                      Home

                                                      Modules

                                                      • diff --git a/docs/api/data_undercomplicate_index.js.html b/docs/api/data_undercomplicate_index.js.html index 40a674e0..76f04606 100644 --- a/docs/api/data_undercomplicate_index.js.html +++ b/docs/api/data_undercomplicate_index.js.html @@ -59,7 +59,7 @@

                                                        Home

                                                        Modules

                                                        • diff --git a/docs/api/data_undercomplicate_joins.js.html b/docs/api/data_undercomplicate_joins.js.html index 0b929f81..b58d71a6 100644 --- a/docs/api/data_undercomplicate_joins.js.html +++ b/docs/api/data_undercomplicate_joins.js.html @@ -151,7 +151,7 @@

                                                          Home

                                                          Modules

                                                          • diff --git a/docs/api/data_undercomplicate_lru_cache.js.html b/docs/api/data_undercomplicate_lru_cache.js.html index 31e52c7c..dc14be44 100644 --- a/docs/api/data_undercomplicate_lru_cache.js.html +++ b/docs/api/data_undercomplicate_lru_cache.js.html @@ -205,7 +205,7 @@

                                                            Home

                                                            Modules

                                                            • diff --git a/docs/api/data_undercomplicate_requests.js.html b/docs/api/data_undercomplicate_requests.js.html index 93446489..bf0349a2 100644 --- a/docs/api/data_undercomplicate_requests.js.html +++ b/docs/api/data_undercomplicate_requests.js.html @@ -141,7 +141,7 @@

                                                              Home

                                                              Modules

                                                              • diff --git a/docs/api/data_undercomplicate_util.js.html b/docs/api/data_undercomplicate_util.js.html index e8413ec5..470ef951 100644 --- a/docs/api/data_undercomplicate_util.js.html +++ b/docs/api/data_undercomplicate_util.js.html @@ -61,7 +61,7 @@

                                                                Home

                                                                Modules

                                                                • diff --git a/docs/api/ext_lz-credible-sets.js.html b/docs/api/ext_lz-credible-sets.js.html index 1505c4a4..00437061 100644 --- a/docs/api/ext_lz-credible-sets.js.html +++ b/docs/api/ext_lz-credible-sets.js.html @@ -464,7 +464,7 @@

                                                                  Home

                                                                  Modules

                                                                  • diff --git a/docs/api/ext_lz-dynamic-urls.js.html b/docs/api/ext_lz-dynamic-urls.js.html index 9a474622..7fd7b710 100644 --- a/docs/api/ext_lz-dynamic-urls.js.html +++ b/docs/api/ext_lz-dynamic-urls.js.html @@ -248,7 +248,7 @@

                                                                    Home

                                                                    Modules

                                                                    • diff --git a/docs/api/ext_lz-forest-track.js.html b/docs/api/ext_lz-forest-track.js.html index 41dfd54c..66792519 100644 --- a/docs/api/ext_lz-forest-track.js.html +++ b/docs/api/ext_lz-forest-track.js.html @@ -317,7 +317,7 @@

                                                                      Home

                                                                      Modules

                                                                      • diff --git a/docs/api/ext_lz-intervals-enrichment.js.html b/docs/api/ext_lz-intervals-enrichment.js.html index f1027c8a..c4e6fe66 100644 --- a/docs/api/ext_lz-intervals-enrichment.js.html +++ b/docs/api/ext_lz-intervals-enrichment.js.html @@ -368,7 +368,7 @@

                                                                        Home

                                                                        Modules

                                                                        • diff --git a/docs/api/ext_lz-intervals-track.js.html b/docs/api/ext_lz-intervals-track.js.html index b595f24d..40f4bf76 100644 --- a/docs/api/ext_lz-intervals-track.js.html +++ b/docs/api/ext_lz-intervals-track.js.html @@ -847,7 +847,7 @@

                                                                          Home

                                                                          Modules

                                                                          • diff --git a/docs/api/ext_lz-parsers_bed.js.html b/docs/api/ext_lz-parsers_bed.js.html index 7e9e6080..7855ee9e 100644 --- a/docs/api/ext_lz-parsers_bed.js.html +++ b/docs/api/ext_lz-parsers_bed.js.html @@ -156,7 +156,7 @@

                                                                            Home

                                                                            Modules

                                                                            • diff --git a/docs/api/ext_lz-parsers_gwas_parsers.js.html b/docs/api/ext_lz-parsers_gwas_parsers.js.html index 7790ac1a..ed6b367c 100644 --- a/docs/api/ext_lz-parsers_gwas_parsers.js.html +++ b/docs/api/ext_lz-parsers_gwas_parsers.js.html @@ -219,7 +219,7 @@

                                                                              Home

                                                                              Modules

                                                                              • diff --git a/docs/api/ext_lz-parsers_index.js.html b/docs/api/ext_lz-parsers_index.js.html index 2459c9b7..ea3b95d8 100644 --- a/docs/api/ext_lz-parsers_index.js.html +++ b/docs/api/ext_lz-parsers_index.js.html @@ -143,7 +143,7 @@

                                                                                Home

                                                                                Modules

                                                                                • diff --git a/docs/api/ext_lz-parsers_ld.js.html b/docs/api/ext_lz-parsers_ld.js.html index ac8848c3..e7b2fe51 100644 --- a/docs/api/ext_lz-parsers_ld.js.html +++ b/docs/api/ext_lz-parsers_ld.js.html @@ -79,7 +79,7 @@

                                                                                  Home

                                                                                  Modules

                                                                                  • diff --git a/docs/api/ext_lz-tabix-source.js.html b/docs/api/ext_lz-tabix-source.js.html index 4f3a4489..0b6f8ce1 100644 --- a/docs/api/ext_lz-tabix-source.js.html +++ b/docs/api/ext_lz-tabix-source.js.html @@ -175,7 +175,7 @@

                                                                                    Home

                                                                                    Modules

                                                                                    • diff --git a/docs/api/ext_lz-widget-addons.js.html b/docs/api/ext_lz-widget-addons.js.html index 8e0ce95f..64ce8432 100644 --- a/docs/api/ext_lz-widget-addons.js.html +++ b/docs/api/ext_lz-widget-addons.js.html @@ -372,7 +372,7 @@

                                                                                      Home

                                                                                      Modules

                                                                                      • diff --git a/docs/api/global.html b/docs/api/global.html index 4f06a34a..e77c776e 100644 --- a/docs/api/global.html +++ b/docs/api/global.html @@ -2810,7 +2810,7 @@

                                                                                        Home

                                                                                        Modules

                                                                                        • diff --git a/docs/api/helpers_common.js.html b/docs/api/helpers_common.js.html index 3317f9f4..73792832 100644 --- a/docs/api/helpers_common.js.html +++ b/docs/api/helpers_common.js.html @@ -297,7 +297,7 @@

                                                                                          Home

                                                                                          Modules

                                                                                          • diff --git a/docs/api/helpers_display.js.html b/docs/api/helpers_display.js.html index 9968a5a4..4f9328d6 100644 --- a/docs/api/helpers_display.js.html +++ b/docs/api/helpers_display.js.html @@ -400,7 +400,7 @@

                                                                                            Home

                                                                                            Modules

                                                                                            • diff --git a/docs/api/helpers_jsonpath.js.html b/docs/api/helpers_jsonpath.js.html index c642328e..7b37f2b8 100644 --- a/docs/api/helpers_jsonpath.js.html +++ b/docs/api/helpers_jsonpath.js.html @@ -270,7 +270,7 @@

                                                                                              Home

                                                                                              Modules

                                                                                              • diff --git a/docs/api/helpers_layouts.js.html b/docs/api/helpers_layouts.js.html index fab9c739..c97cfada 100644 --- a/docs/api/helpers_layouts.js.html +++ b/docs/api/helpers_layouts.js.html @@ -301,7 +301,7 @@

                                                                                                Home

                                                                                                Modules

                                                                                                • diff --git a/docs/api/helpers_render.js.html b/docs/api/helpers_render.js.html index a2df5e42..9f060944 100644 --- a/docs/api/helpers_render.js.html +++ b/docs/api/helpers_render.js.html @@ -127,7 +127,7 @@

                                                                                                  Home

                                                                                                  Modules

                                                                                                  • diff --git a/docs/api/helpers_scalable.js.html b/docs/api/helpers_scalable.js.html index a5f94514..f50c407d 100644 --- a/docs/api/helpers_scalable.js.html +++ b/docs/api/helpers_scalable.js.html @@ -299,7 +299,7 @@

                                                                                                    Home

                                                                                                    Modules

                                                                                                    • diff --git a/docs/api/helpers_transforms.js.html b/docs/api/helpers_transforms.js.html index 1a03ef29..ee6ece90 100644 --- a/docs/api/helpers_transforms.js.html +++ b/docs/api/helpers_transforms.js.html @@ -190,7 +190,7 @@

                                                                                                      Home

                                                                                                      Modules

                                                                                                      • diff --git a/docs/api/index.html b/docs/api/index.html index 50e5e5c8..0f267a3f 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -227,7 +227,7 @@

                                                                                                        Home

                                                                                                        Modules

                                                                                                        • diff --git a/docs/api/index.js.html b/docs/api/index.js.html index 0b3e4b02..ec93144f 100644 --- a/docs/api/index.js.html +++ b/docs/api/index.js.html @@ -124,7 +124,7 @@

                                                                                                          Home

                                                                                                          Modules

                                                                                                          • diff --git a/docs/api/layouts_index.js.html b/docs/api/layouts_index.js.html index 1aa57924..38857986 100644 --- a/docs/api/layouts_index.js.html +++ b/docs/api/layouts_index.js.html @@ -1189,7 +1189,7 @@

                                                                                                            Home

                                                                                                            Modules

                                                                                                            • diff --git a/docs/api/module-LocusZoom-DataSources.html b/docs/api/module-LocusZoom-DataSources.html index cd58e5c7..710295c8 100644 --- a/docs/api/module-LocusZoom-DataSources.html +++ b/docs/api/module-LocusZoom-DataSources.html @@ -1100,7 +1100,7 @@

                                                                                                              Home

                                                                                                              Modules

                                                                                                              • diff --git a/docs/api/module-LocusZoom.html b/docs/api/module-LocusZoom.html index f45cd810..eab9d458 100644 --- a/docs/api/module-LocusZoom.html +++ b/docs/api/module-LocusZoom.html @@ -1198,7 +1198,7 @@

                                                                                                                Home

                                                                                                                Modules

                                                                                                                • diff --git a/docs/api/module-LocusZoom_Adapters-AssociationLZ.html b/docs/api/module-LocusZoom_Adapters-AssociationLZ.html index 79600334..2386b750 100644 --- a/docs/api/module-LocusZoom_Adapters-AssociationLZ.html +++ b/docs/api/module-LocusZoom_Adapters-AssociationLZ.html @@ -219,7 +219,7 @@

                                                                                                                  Home

                                                                                                                  Modules

                                                                                                                  • diff --git a/docs/api/module-LocusZoom_Adapters-BaseAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseAdapter.html index 86c77af3..285e2c55 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseAdapter.html @@ -164,7 +164,7 @@

                                                                                                                    Home

                                                                                                                    Modules

                                                                                                                    • diff --git a/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html index 93cdb0de..843bddac 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseApiAdapter.html @@ -225,7 +225,7 @@

                                                                                                                      Home

                                                                                                                      Modules

                                                                                                                      • diff --git a/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html index d4ed802d..7dd1b2fa 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseLZAdapter.html @@ -1800,7 +1800,7 @@

                                                                                                                        Home

                                                                                                                        Modules

                                                                                                                        • diff --git a/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html b/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html index 15bba9cb..c0b47bd8 100644 --- a/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html +++ b/docs/api/module-LocusZoom_Adapters-BaseUMAdapter.html @@ -1700,7 +1700,7 @@

                                                                                                                          Home

                                                                                                                          Modules

                                                                                                                          • diff --git a/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html b/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html index d9ae7bd1..ff5d48d4 100644 --- a/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html +++ b/docs/api/module-LocusZoom_Adapters-CredibleSetLZ.html @@ -283,7 +283,7 @@

                                                                                                                            Home

                                                                                                                            Modules

                                                                                                                            • diff --git a/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html b/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html index 1007a9ed..adbd3e85 100644 --- a/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html +++ b/docs/api/module-LocusZoom_Adapters-GeneConstraintLZ.html @@ -352,7 +352,7 @@

                                                                                                                              Home

                                                                                                                              Modules

                                                                                                                              • diff --git a/docs/api/module-LocusZoom_Adapters-GeneLZ.html b/docs/api/module-LocusZoom_Adapters-GeneLZ.html index cc74b00f..396469d6 100644 --- a/docs/api/module-LocusZoom_Adapters-GeneLZ.html +++ b/docs/api/module-LocusZoom_Adapters-GeneLZ.html @@ -383,7 +383,7 @@

                                                                                                                                Home

                                                                                                                                Modules

                                                                                                                                • diff --git a/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html b/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html index f2fc1f42..ec040dee 100644 --- a/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html +++ b/docs/api/module-LocusZoom_Adapters-GwasCatalogLZ.html @@ -389,7 +389,7 @@

                                                                                                                                  Home

                                                                                                                                  Modules

                                                                                                                                  • diff --git a/docs/api/module-LocusZoom_Adapters-IntervalLZ.html b/docs/api/module-LocusZoom_Adapters-IntervalLZ.html index 58f7d838..2b388b6a 100644 --- a/docs/api/module-LocusZoom_Adapters-IntervalLZ.html +++ b/docs/api/module-LocusZoom_Adapters-IntervalLZ.html @@ -220,7 +220,7 @@

                                                                                                                                    Home

                                                                                                                                    Modules

                                                                                                                                    • diff --git a/docs/api/module-LocusZoom_Adapters-LDServer.html b/docs/api/module-LocusZoom_Adapters-LDServer.html index 06bce955..83875309 100644 --- a/docs/api/module-LocusZoom_Adapters-LDServer.html +++ b/docs/api/module-LocusZoom_Adapters-LDServer.html @@ -383,7 +383,7 @@

                                                                                                                                      Home

                                                                                                                                      Modules

                                                                                                                                      • diff --git a/docs/api/module-LocusZoom_Adapters-PheWASLZ.html b/docs/api/module-LocusZoom_Adapters-PheWASLZ.html index 00a12c4d..57533992 100644 --- a/docs/api/module-LocusZoom_Adapters-PheWASLZ.html +++ b/docs/api/module-LocusZoom_Adapters-PheWASLZ.html @@ -242,7 +242,7 @@

                                                                                                                                        Home

                                                                                                                                        Modules

                                                                                                                                        • diff --git a/docs/api/module-LocusZoom_Adapters-RecombLZ.html b/docs/api/module-LocusZoom_Adapters-RecombLZ.html index 6c313970..792f0ef4 100644 --- a/docs/api/module-LocusZoom_Adapters-RecombLZ.html +++ b/docs/api/module-LocusZoom_Adapters-RecombLZ.html @@ -383,7 +383,7 @@

                                                                                                                                          Home

                                                                                                                                          Modules

                                                                                                                                          • diff --git a/docs/api/module-LocusZoom_Adapters-StaticSource.html b/docs/api/module-LocusZoom_Adapters-StaticSource.html index bc9826dc..933fffcb 100644 --- a/docs/api/module-LocusZoom_Adapters-StaticSource.html +++ b/docs/api/module-LocusZoom_Adapters-StaticSource.html @@ -224,7 +224,7 @@

                                                                                                                                            Home

                                                                                                                                            Modules

                                                                                                                                            • diff --git a/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html b/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html index 236aaa75..ee63c64e 100644 --- a/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html +++ b/docs/api/module-LocusZoom_Adapters-TabixUrlSource.html @@ -392,7 +392,7 @@

                                                                                                                                              Home

                                                                                                                                              Modules

                                                                                                                                              • diff --git a/docs/api/module-LocusZoom_Adapters-UserTabixLD.html b/docs/api/module-LocusZoom_Adapters-UserTabixLD.html index 423ac710..e007ec91 100644 --- a/docs/api/module-LocusZoom_Adapters-UserTabixLD.html +++ b/docs/api/module-LocusZoom_Adapters-UserTabixLD.html @@ -192,7 +192,7 @@

                                                                                                                                                Home

                                                                                                                                                Modules

                                                                                                                                                • diff --git a/docs/api/module-LocusZoom_Adapters.html b/docs/api/module-LocusZoom_Adapters.html index 16394336..2ec69a73 100644 --- a/docs/api/module-LocusZoom_Adapters.html +++ b/docs/api/module-LocusZoom_Adapters.html @@ -371,7 +371,7 @@

                                                                                                                                                  Home

                                                                                                                                                  Modules

                                                                                                                                                  • diff --git a/docs/api/module-LocusZoom_DataFunctions.html b/docs/api/module-LocusZoom_DataFunctions.html index cb2d4677..3fedc37f 100644 --- a/docs/api/module-LocusZoom_DataFunctions.html +++ b/docs/api/module-LocusZoom_DataFunctions.html @@ -1123,7 +1123,7 @@

                                                                                                                                                    Home

                                                                                                                                                    Modules

                                                                                                                                                    • diff --git a/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html b/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html index 8bdb7cff..86d197c8 100644 --- a/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html +++ b/docs/api/module-LocusZoom_DataLayers-BaseDataLayer.html @@ -4195,7 +4195,7 @@

                                                                                                                                                      Home

                                                                                                                                                      Modules

                                                                                                                                                      • diff --git a/docs/api/module-LocusZoom_DataLayers-annotation_track.html b/docs/api/module-LocusZoom_DataLayers-annotation_track.html index 78d66d4d..ba7492b6 100644 --- a/docs/api/module-LocusZoom_DataLayers-annotation_track.html +++ b/docs/api/module-LocusZoom_DataLayers-annotation_track.html @@ -397,7 +397,7 @@

                                                                                                                                                        Home

                                                                                                                                                        Modules

                                                                                                                                                        • diff --git a/docs/api/module-LocusZoom_DataLayers-arcs.html b/docs/api/module-LocusZoom_DataLayers-arcs.html index 717aa76f..2d2b14a3 100644 --- a/docs/api/module-LocusZoom_DataLayers-arcs.html +++ b/docs/api/module-LocusZoom_DataLayers-arcs.html @@ -628,7 +628,7 @@

                                                                                                                                                          Home

                                                                                                                                                          Modules

                                                                                                                                                          • diff --git a/docs/api/module-LocusZoom_DataLayers-category_forest.html b/docs/api/module-LocusZoom_DataLayers-category_forest.html index b2a2ca5a..311a4ca1 100644 --- a/docs/api/module-LocusZoom_DataLayers-category_forest.html +++ b/docs/api/module-LocusZoom_DataLayers-category_forest.html @@ -173,7 +173,7 @@

                                                                                                                                                            Home

                                                                                                                                                            Modules

                                                                                                                                                            • diff --git a/docs/api/module-LocusZoom_DataLayers-category_scatter.html b/docs/api/module-LocusZoom_DataLayers-category_scatter.html index b41ef571..c4f0841f 100644 --- a/docs/api/module-LocusZoom_DataLayers-category_scatter.html +++ b/docs/api/module-LocusZoom_DataLayers-category_scatter.html @@ -481,7 +481,7 @@

                                                                                                                                                              Home

                                                                                                                                                              Modules

                                                                                                                                                              • diff --git a/docs/api/module-LocusZoom_DataLayers-forest.html b/docs/api/module-LocusZoom_DataLayers-forest.html index 9878f952..92cdad64 100644 --- a/docs/api/module-LocusZoom_DataLayers-forest.html +++ b/docs/api/module-LocusZoom_DataLayers-forest.html @@ -604,7 +604,7 @@

                                                                                                                                                                Home

                                                                                                                                                                Modules

                                                                                                                                                                • diff --git a/docs/api/module-LocusZoom_DataLayers-genes.html b/docs/api/module-LocusZoom_DataLayers-genes.html index 86aa0ab6..8827c32a 100644 --- a/docs/api/module-LocusZoom_DataLayers-genes.html +++ b/docs/api/module-LocusZoom_DataLayers-genes.html @@ -1230,7 +1230,7 @@

                                                                                                                                                                  Home

                                                                                                                                                                  Modules

                                                                                                                                                                  • diff --git a/docs/api/module-LocusZoom_DataLayers-highlight_regions.html b/docs/api/module-LocusZoom_DataLayers-highlight_regions.html index c8a3d19f..32d25cd9 100644 --- a/docs/api/module-LocusZoom_DataLayers-highlight_regions.html +++ b/docs/api/module-LocusZoom_DataLayers-highlight_regions.html @@ -548,7 +548,7 @@

                                                                                                                                                                    Home

                                                                                                                                                                    Modules

                                                                                                                                                                    • diff --git a/docs/api/module-LocusZoom_DataLayers-intervals.html b/docs/api/module-LocusZoom_DataLayers-intervals.html index fec1aa23..078f2051 100644 --- a/docs/api/module-LocusZoom_DataLayers-intervals.html +++ b/docs/api/module-LocusZoom_DataLayers-intervals.html @@ -970,7 +970,7 @@

                                                                                                                                                                      Home

                                                                                                                                                                      Modules

                                                                                                                                                                      • diff --git a/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html b/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html index e495fa1f..d2301bdd 100644 --- a/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html +++ b/docs/api/module-LocusZoom_DataLayers-intervals_enrichment.html @@ -581,7 +581,7 @@

                                                                                                                                                                        Home

                                                                                                                                                                        Modules

                                                                                                                                                                        • diff --git a/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html b/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html index f557c2a3..ca62ed13 100644 --- a/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html +++ b/docs/api/module-LocusZoom_DataLayers-orthogonal_line.html @@ -671,7 +671,7 @@

                                                                                                                                                                          Home

                                                                                                                                                                          Modules

                                                                                                                                                                          • diff --git a/docs/api/module-LocusZoom_DataLayers-scatter.html b/docs/api/module-LocusZoom_DataLayers-scatter.html index 7c137ce0..c54d236f 100644 --- a/docs/api/module-LocusZoom_DataLayers-scatter.html +++ b/docs/api/module-LocusZoom_DataLayers-scatter.html @@ -1224,7 +1224,7 @@

                                                                                                                                                                            Home

                                                                                                                                                                            Modules

                                                                                                                                                                            • diff --git a/docs/api/module-LocusZoom_DataLayers.html b/docs/api/module-LocusZoom_DataLayers.html index dc24f594..e72cada4 100644 --- a/docs/api/module-LocusZoom_DataLayers.html +++ b/docs/api/module-LocusZoom_DataLayers.html @@ -1579,7 +1579,7 @@

                                                                                                                                                                              Home

                                                                                                                                                                              Modules

                                                                                                                                                                              • diff --git a/docs/api/module-LocusZoom_Layouts.html b/docs/api/module-LocusZoom_Layouts.html index 290a8d08..6bfd7a81 100644 --- a/docs/api/module-LocusZoom_Layouts.html +++ b/docs/api/module-LocusZoom_Layouts.html @@ -3325,7 +3325,7 @@

                                                                                                                                                                                Home

                                                                                                                                                                                Modules

                                                                                                                                                                                • diff --git a/docs/api/module-LocusZoom_MatchFunctions.html b/docs/api/module-LocusZoom_MatchFunctions.html index 12a94623..7b1d0b33 100644 --- a/docs/api/module-LocusZoom_MatchFunctions.html +++ b/docs/api/module-LocusZoom_MatchFunctions.html @@ -1538,7 +1538,7 @@

                                                                                                                                                                                  Home

                                                                                                                                                                                  Modules

                                                                                                                                                                                  • diff --git a/docs/api/module-LocusZoom_ScaleFunctions.html b/docs/api/module-LocusZoom_ScaleFunctions.html index 5384ba88..bf6453b0 100644 --- a/docs/api/module-LocusZoom_ScaleFunctions.html +++ b/docs/api/module-LocusZoom_ScaleFunctions.html @@ -1964,7 +1964,7 @@

                                                                                                                                                                                    Home

                                                                                                                                                                                    Modules

                                                                                                                                                                                    • diff --git a/docs/api/module-LocusZoom_TransformationFunctions.html b/docs/api/module-LocusZoom_TransformationFunctions.html index 38effa68..435c890c 100644 --- a/docs/api/module-LocusZoom_TransformationFunctions.html +++ b/docs/api/module-LocusZoom_TransformationFunctions.html @@ -1263,7 +1263,7 @@

                                                                                                                                                                                      Home

                                                                                                                                                                                      Modules

                                                                                                                                                                                      • diff --git a/docs/api/module-LocusZoom_Widgets-BaseWidget.html b/docs/api/module-LocusZoom_Widgets-BaseWidget.html index f9db92ca..5e6d79eb 100644 --- a/docs/api/module-LocusZoom_Widgets-BaseWidget.html +++ b/docs/api/module-LocusZoom_Widgets-BaseWidget.html @@ -1660,7 +1660,7 @@

                                                                                                                                                                                        Home

                                                                                                                                                                                        Modules

                                                                                                                                                                                        • diff --git a/docs/api/module-LocusZoom_Widgets-_Button.html b/docs/api/module-LocusZoom_Widgets-_Button.html index 511ac229..3398cbc0 100644 --- a/docs/api/module-LocusZoom_Widgets-_Button.html +++ b/docs/api/module-LocusZoom_Widgets-_Button.html @@ -3467,7 +3467,7 @@

                                                                                                                                                                                          Home

                                                                                                                                                                                          Modules

                                                                                                                                                                                          • diff --git a/docs/api/module-LocusZoom_Widgets-display_options.html b/docs/api/module-LocusZoom_Widgets-display_options.html index d7d2c17d..b4f68fe6 100644 --- a/docs/api/module-LocusZoom_Widgets-display_options.html +++ b/docs/api/module-LocusZoom_Widgets-display_options.html @@ -473,7 +473,7 @@

                                                                                                                                                                                            Home

                                                                                                                                                                                            Modules

                                                                                                                                                                                            • diff --git a/docs/api/module-LocusZoom_Widgets-download_png.html b/docs/api/module-LocusZoom_Widgets-download_png.html index a9b059c6..50673e4d 100644 --- a/docs/api/module-LocusZoom_Widgets-download_png.html +++ b/docs/api/module-LocusZoom_Widgets-download_png.html @@ -376,7 +376,7 @@

                                                                                                                                                                                              Home

                                                                                                                                                                                              Modules

                                                                                                                                                                                              • diff --git a/docs/api/module-LocusZoom_Widgets-download_svg.html b/docs/api/module-LocusZoom_Widgets-download_svg.html index e014b60f..43ccee59 100644 --- a/docs/api/module-LocusZoom_Widgets-download_svg.html +++ b/docs/api/module-LocusZoom_Widgets-download_svg.html @@ -465,7 +465,7 @@

                                                                                                                                                                                                Home

                                                                                                                                                                                                Modules

                                                                                                                                                                                                • diff --git a/docs/api/module-LocusZoom_Widgets-filter_field.html b/docs/api/module-LocusZoom_Widgets-filter_field.html index ba27baf9..115cbf74 100644 --- a/docs/api/module-LocusZoom_Widgets-filter_field.html +++ b/docs/api/module-LocusZoom_Widgets-filter_field.html @@ -674,7 +674,7 @@

                                                                                                                                                                                                  Home

                                                                                                                                                                                                  Modules

                                                                                                                                                                                                  • diff --git a/docs/api/module-LocusZoom_Widgets-menu.html b/docs/api/module-LocusZoom_Widgets-menu.html index b13718d1..75f7b373 100644 --- a/docs/api/module-LocusZoom_Widgets-menu.html +++ b/docs/api/module-LocusZoom_Widgets-menu.html @@ -259,7 +259,7 @@

                                                                                                                                                                                                    Home

                                                                                                                                                                                                    Modules

                                                                                                                                                                                                    • diff --git a/docs/api/module-LocusZoom_Widgets-move_panel_down.html b/docs/api/module-LocusZoom_Widgets-move_panel_down.html index a9e2d08c..1ea273ff 100644 --- a/docs/api/module-LocusZoom_Widgets-move_panel_down.html +++ b/docs/api/module-LocusZoom_Widgets-move_panel_down.html @@ -170,7 +170,7 @@

                                                                                                                                                                                                      Home

                                                                                                                                                                                                      Modules

                                                                                                                                                                                                      • diff --git a/docs/api/module-LocusZoom_Widgets-move_panel_up.html b/docs/api/module-LocusZoom_Widgets-move_panel_up.html index 55de4ce5..974522d6 100644 --- a/docs/api/module-LocusZoom_Widgets-move_panel_up.html +++ b/docs/api/module-LocusZoom_Widgets-move_panel_up.html @@ -170,7 +170,7 @@

                                                                                                                                                                                                        Home

                                                                                                                                                                                                        Modules

                                                                                                                                                                                                        • diff --git a/docs/api/module-LocusZoom_Widgets-region_scale.html b/docs/api/module-LocusZoom_Widgets-region_scale.html index 15fe3e8f..585920bb 100644 --- a/docs/api/module-LocusZoom_Widgets-region_scale.html +++ b/docs/api/module-LocusZoom_Widgets-region_scale.html @@ -173,7 +173,7 @@

                                                                                                                                                                                                          Home

                                                                                                                                                                                                          Modules

                                                                                                                                                                                                          • diff --git a/docs/api/module-LocusZoom_Widgets-remove_panel.html b/docs/api/module-LocusZoom_Widgets-remove_panel.html index ad197c8d..f0cda446 100644 --- a/docs/api/module-LocusZoom_Widgets-remove_panel.html +++ b/docs/api/module-LocusZoom_Widgets-remove_panel.html @@ -239,7 +239,7 @@

                                                                                                                                                                                                            Home

                                                                                                                                                                                                            Modules

                                                                                                                                                                                                            • diff --git a/docs/api/module-LocusZoom_Widgets-resize_to_data.html b/docs/api/module-LocusZoom_Widgets-resize_to_data.html index e1fe6751..ee8753e0 100644 --- a/docs/api/module-LocusZoom_Widgets-resize_to_data.html +++ b/docs/api/module-LocusZoom_Widgets-resize_to_data.html @@ -268,7 +268,7 @@

                                                                                                                                                                                                              Home

                                                                                                                                                                                                              Modules

                                                                                                                                                                                                              • diff --git a/docs/api/module-LocusZoom_Widgets-set_state.html b/docs/api/module-LocusZoom_Widgets-set_state.html index 4ea9ae2a..9ff8721b 100644 --- a/docs/api/module-LocusZoom_Widgets-set_state.html +++ b/docs/api/module-LocusZoom_Widgets-set_state.html @@ -422,7 +422,7 @@

                                                                                                                                                                                                                Home

                                                                                                                                                                                                                Modules

                                                                                                                                                                                                                • diff --git a/docs/api/module-LocusZoom_Widgets-shift_region.html b/docs/api/module-LocusZoom_Widgets-shift_region.html index 1519a535..457fd166 100644 --- a/docs/api/module-LocusZoom_Widgets-shift_region.html +++ b/docs/api/module-LocusZoom_Widgets-shift_region.html @@ -312,7 +312,7 @@

                                                                                                                                                                                                                  Home

                                                                                                                                                                                                                  Modules

                                                                                                                                                                                                                  • diff --git a/docs/api/module-LocusZoom_Widgets-title.html b/docs/api/module-LocusZoom_Widgets-title.html index 52078c06..8d360f0a 100644 --- a/docs/api/module-LocusZoom_Widgets-title.html +++ b/docs/api/module-LocusZoom_Widgets-title.html @@ -261,7 +261,7 @@

                                                                                                                                                                                                                    Home

                                                                                                                                                                                                                    Modules

                                                                                                                                                                                                                    • diff --git a/docs/api/module-LocusZoom_Widgets-toggle_legend.html b/docs/api/module-LocusZoom_Widgets-toggle_legend.html index 00782acc..52045a53 100644 --- a/docs/api/module-LocusZoom_Widgets-toggle_legend.html +++ b/docs/api/module-LocusZoom_Widgets-toggle_legend.html @@ -169,7 +169,7 @@

                                                                                                                                                                                                                      Home

                                                                                                                                                                                                                      Modules

                                                                                                                                                                                                                      • diff --git a/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html b/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html index ed075de9..84d550da 100644 --- a/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html +++ b/docs/api/module-LocusZoom_Widgets-toggle_split_tracks.html @@ -221,7 +221,7 @@

                                                                                                                                                                                                                        Home

                                                                                                                                                                                                                        Modules

                                                                                                                                                                                                                        • diff --git a/docs/api/module-LocusZoom_Widgets-zoom_region.html b/docs/api/module-LocusZoom_Widgets-zoom_region.html index 1dd1353b..f19e9e42 100644 --- a/docs/api/module-LocusZoom_Widgets-zoom_region.html +++ b/docs/api/module-LocusZoom_Widgets-zoom_region.html @@ -312,7 +312,7 @@

                                                                                                                                                                                                                          Home

                                                                                                                                                                                                                          Modules

                                                                                                                                                                                                                          • diff --git a/docs/api/module-LocusZoom_Widgets.html b/docs/api/module-LocusZoom_Widgets.html index 1d3cbd1a..05839c87 100644 --- a/docs/api/module-LocusZoom_Widgets.html +++ b/docs/api/module-LocusZoom_Widgets.html @@ -1246,7 +1246,7 @@

                                                                                                                                                                                                                            Home

                                                                                                                                                                                                                            Modules

                                                                                                                                                                                                                            • diff --git a/docs/api/module-components_legend-Legend.html b/docs/api/module-components_legend-Legend.html index d9065a1f..3da1ec8a 100644 --- a/docs/api/module-components_legend-Legend.html +++ b/docs/api/module-components_legend-Legend.html @@ -1154,7 +1154,7 @@

                                                                                                                                                                                                                              Home

                                                                                                                                                                                                                              Modules

                                                                                                                                                                                                                              • diff --git a/docs/api/module-data_requester-DataOperation.html b/docs/api/module-data_requester-DataOperation.html index 76535dbb..40f875f0 100644 --- a/docs/api/module-data_requester-DataOperation.html +++ b/docs/api/module-data_requester-DataOperation.html @@ -246,7 +246,7 @@

                                                                                                                                                                                                                                Home

                                                                                                                                                                                                                                Modules

                                                                                                                                                                                                                                • diff --git a/docs/api/module-ext_lz-credible-sets.html b/docs/api/module-ext_lz-credible-sets.html index ef60a589..c5af2156 100644 --- a/docs/api/module-ext_lz-credible-sets.html +++ b/docs/api/module-ext_lz-credible-sets.html @@ -181,7 +181,7 @@

                                                                                                                                                                                                                                  Home

                                                                                                                                                                                                                                  Modules

                                                                                                                                                                                                                                  • diff --git a/docs/api/module-ext_lz-dynamic-urls.html b/docs/api/module-ext_lz-dynamic-urls.html index bbe9e1f1..a544241e 100644 --- a/docs/api/module-ext_lz-dynamic-urls.html +++ b/docs/api/module-ext_lz-dynamic-urls.html @@ -883,7 +883,7 @@

                                                                                                                                                                                                                                    Home

                                                                                                                                                                                                                                    Modules

                                                                                                                                                                                                                                    • diff --git a/docs/api/module-ext_lz-forest-track.html b/docs/api/module-ext_lz-forest-track.html index 2ab0a040..58a3c047 100644 --- a/docs/api/module-ext_lz-forest-track.html +++ b/docs/api/module-ext_lz-forest-track.html @@ -176,7 +176,7 @@

                                                                                                                                                                                                                                      Home

                                                                                                                                                                                                                                      Modules

                                                                                                                                                                                                                                      • diff --git a/docs/api/module-ext_lz-intervals-enrichment.html b/docs/api/module-ext_lz-intervals-enrichment.html index ae2c7571..8bad5e5d 100644 --- a/docs/api/module-ext_lz-intervals-enrichment.html +++ b/docs/api/module-ext_lz-intervals-enrichment.html @@ -179,7 +179,7 @@

                                                                                                                                                                                                                                        Home

                                                                                                                                                                                                                                        Modules

                                                                                                                                                                                                                                        • diff --git a/docs/api/module-ext_lz-intervals-track.html b/docs/api/module-ext_lz-intervals-track.html index 553d918f..74233520 100644 --- a/docs/api/module-ext_lz-intervals-track.html +++ b/docs/api/module-ext_lz-intervals-track.html @@ -184,7 +184,7 @@

                                                                                                                                                                                                                                          Home

                                                                                                                                                                                                                                          Modules

                                                                                                                                                                                                                                          • diff --git a/docs/api/module-ext_lz-parsers.html b/docs/api/module-ext_lz-parsers.html index eea731e2..1e2fad31 100644 --- a/docs/api/module-ext_lz-parsers.html +++ b/docs/api/module-ext_lz-parsers.html @@ -1271,7 +1271,7 @@

                                                                                                                                                                                                                                            Home

                                                                                                                                                                                                                                            Modules

                                                                                                                                                                                                                                            • diff --git a/docs/api/module-ext_lz-tabix-source.html b/docs/api/module-ext_lz-tabix-source.html index a5b1dfde..3f232a6c 100644 --- a/docs/api/module-ext_lz-tabix-source.html +++ b/docs/api/module-ext_lz-tabix-source.html @@ -185,7 +185,7 @@

                                                                                                                                                                                                                                              Home

                                                                                                                                                                                                                                              Modules

                                                                                                                                                                                                                                              • diff --git a/docs/api/module-ext_lz-widget-addons-covariates_model.html b/docs/api/module-ext_lz-widget-addons-covariates_model.html index 7f5649d9..9e6ca5b6 100644 --- a/docs/api/module-ext_lz-widget-addons-covariates_model.html +++ b/docs/api/module-ext_lz-widget-addons-covariates_model.html @@ -293,7 +293,7 @@

                                                                                                                                                                                                                                                Home

                                                                                                                                                                                                                                                Modules

                                                                                                                                                                                                                                                • diff --git a/docs/api/module-ext_lz-widget-addons-data_layers.html b/docs/api/module-ext_lz-widget-addons-data_layers.html index 5118a845..6d8510b4 100644 --- a/docs/api/module-ext_lz-widget-addons-data_layers.html +++ b/docs/api/module-ext_lz-widget-addons-data_layers.html @@ -169,7 +169,7 @@

                                                                                                                                                                                                                                                  Home

                                                                                                                                                                                                                                                  Modules

                                                                                                                                                                                                                                                  • diff --git a/docs/api/module-ext_lz-widget-addons.html b/docs/api/module-ext_lz-widget-addons.html index cb8793a9..7bce406e 100644 --- a/docs/api/module-ext_lz-widget-addons.html +++ b/docs/api/module-ext_lz-widget-addons.html @@ -188,7 +188,7 @@

                                                                                                                                                                                                                                                    Home

                                                                                                                                                                                                                                                    Modules

                                                                                                                                                                                                                                                    • diff --git a/docs/api/module-registry_base-RegistryBase.html b/docs/api/module-registry_base-RegistryBase.html index 8d86c3ca..3b2bd63f 100644 --- a/docs/api/module-registry_base-RegistryBase.html +++ b/docs/api/module-registry_base-RegistryBase.html @@ -993,7 +993,7 @@

                                                                                                                                                                                                                                                      Home

                                                                                                                                                                                                                                                      Modules

                                                                                                                                                                                                                                                      • diff --git a/docs/api/module-undercomplicate.BaseAdapter.html b/docs/api/module-undercomplicate.BaseAdapter.html index 102915e7..d702be68 100644 --- a/docs/api/module-undercomplicate.BaseAdapter.html +++ b/docs/api/module-undercomplicate.BaseAdapter.html @@ -1488,7 +1488,7 @@

                                                                                                                                                                                                                                                        Home

                                                                                                                                                                                                                                                        Modules

                                                                                                                                                                                                                                                        • diff --git a/docs/api/module-undercomplicate.BaseUrlAdapter.html b/docs/api/module-undercomplicate.BaseUrlAdapter.html index 9befc760..91561dad 100644 --- a/docs/api/module-undercomplicate.BaseUrlAdapter.html +++ b/docs/api/module-undercomplicate.BaseUrlAdapter.html @@ -1401,7 +1401,7 @@

                                                                                                                                                                                                                                                          Home

                                                                                                                                                                                                                                                          Modules

                                                                                                                                                                                                                                                          • diff --git a/docs/api/module-undercomplicate.LRUCache.html b/docs/api/module-undercomplicate.LRUCache.html index c81af78d..bd80b9ff 100644 --- a/docs/api/module-undercomplicate.LRUCache.html +++ b/docs/api/module-undercomplicate.LRUCache.html @@ -231,7 +231,7 @@

                                                                                                                                                                                                                                                            Home

                                                                                                                                                                                                                                                            Modules

                                                                                                                                                                                                                                                            • diff --git a/docs/api/module-undercomplicate.html b/docs/api/module-undercomplicate.html index ad94f3d1..394913fc 100644 --- a/docs/api/module-undercomplicate.html +++ b/docs/api/module-undercomplicate.html @@ -1037,7 +1037,7 @@

                                                                                                                                                                                                                                                              Home

                                                                                                                                                                                                                                                              Modules

                                                                                                                                                                                                                                                              • diff --git a/docs/api/registry_adapters.js.html b/docs/api/registry_adapters.js.html index 5d63a6c7..9b28940b 100644 --- a/docs/api/registry_adapters.js.html +++ b/docs/api/registry_adapters.js.html @@ -86,7 +86,7 @@

                                                                                                                                                                                                                                                                Home

                                                                                                                                                                                                                                                                Modules

                                                                                                                                                                                                                                                                • diff --git a/docs/api/registry_base.js.html b/docs/api/registry_base.js.html index a4eb5ae1..5fb21974 100644 --- a/docs/api/registry_base.js.html +++ b/docs/api/registry_base.js.html @@ -167,7 +167,7 @@

                                                                                                                                                                                                                                                                  Home

                                                                                                                                                                                                                                                                  Modules

                                                                                                                                                                                                                                                                  • diff --git a/docs/api/registry_data_layers.js.html b/docs/api/registry_data_layers.js.html index 5cc85c9d..e4c7f757 100644 --- a/docs/api/registry_data_layers.js.html +++ b/docs/api/registry_data_layers.js.html @@ -61,7 +61,7 @@

                                                                                                                                                                                                                                                                    Home

                                                                                                                                                                                                                                                                    Modules

                                                                                                                                                                                                                                                                    • diff --git a/docs/api/registry_data_ops.js.html b/docs/api/registry_data_ops.js.html index 26fae5c3..8c70058a 100644 --- a/docs/api/registry_data_ops.js.html +++ b/docs/api/registry_data_ops.js.html @@ -215,7 +215,7 @@

                                                                                                                                                                                                                                                                      Home

                                                                                                                                                                                                                                                                      Modules

                                                                                                                                                                                                                                                                      • diff --git a/docs/api/registry_layouts.js.html b/docs/api/registry_layouts.js.html index a6116f4d..9ccb687e 100644 --- a/docs/api/registry_layouts.js.html +++ b/docs/api/registry_layouts.js.html @@ -186,7 +186,7 @@

                                                                                                                                                                                                                                                                        Home

                                                                                                                                                                                                                                                                        Modules

                                                                                                                                                                                                                                                                        • diff --git a/docs/api/registry_matchers.js.html b/docs/api/registry_matchers.js.html index 9431a7da..0a5568ff 100644 --- a/docs/api/registry_matchers.js.html +++ b/docs/api/registry_matchers.js.html @@ -160,7 +160,7 @@

                                                                                                                                                                                                                                                                          Home

                                                                                                                                                                                                                                                                          Modules

                                                                                                                                                                                                                                                                          • diff --git a/docs/api/registry_transforms.js.html b/docs/api/registry_transforms.js.html index 7ebd4167..8c80e7e4 100644 --- a/docs/api/registry_transforms.js.html +++ b/docs/api/registry_transforms.js.html @@ -113,7 +113,7 @@

                                                                                                                                                                                                                                                                            Home

                                                                                                                                                                                                                                                                            Modules

                                                                                                                                                                                                                                                                            • diff --git a/docs/api/registry_widgets.js.html b/docs/api/registry_widgets.js.html index 2d22157f..28666691 100644 --- a/docs/api/registry_widgets.js.html +++ b/docs/api/registry_widgets.js.html @@ -59,7 +59,7 @@

                                                                                                                                                                                                                                                                              Home

                                                                                                                                                                                                                                                                              Modules

                                                                                                                                                                                                                                                                              • diff --git a/esm/version.js b/esm/version.js index 314a8c3c..d56c7b1b 100644 --- a/esm/version.js +++ b/esm/version.js @@ -1 +1 @@ -export default '0.14.0-beta.3'; +export default '0.14.0-beta.4'; diff --git a/index.html b/index.html index 7d51b391..22320ce8 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,7 @@

                                                                                                                                                                                                                                                                                Get LocusZoom.js

                                                                                                                                                                                                                                                                                CSS
                                                                                                                                                                                                                                                                                - https://cdn.jsdelivr.net/npm/locuszoom@0.14.0-beta.3/dist/locuszoom.css + https://cdn.jsdelivr.net/npm/locuszoom@0.14.0-beta.4/dist/locuszoom.css
                                                                                                                                                                                                                                                                                All CSS classes are namespaced to avoid collisions. Use <link crossorigin="anonymous" ...> to ensure that saving images works correctly.
                                                                                                                                                                                                                                                                                @@ -96,7 +96,7 @@
                                                                                                                                                                                                                                                                                Dependencies
                                                                                                                                                                                                                                                                                Javascript
                                                                                                                                                                                                                                                                                diff --git a/package-lock.json b/package-lock.json index b8b9f20c..117de224 100644 --- a/package-lock.json +++ b/package-lock.json @@ -365,9 +365,9 @@ } }, "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", + "version": "7.16.12", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz", + "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -1347,6 +1347,28 @@ "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", "dev": true }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "dev": true + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -2045,12 +2067,6 @@ "node-releases": "^1.1.75" }, "dependencies": { - "caniuse-lite": { - "version": "1.0.30001251", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz", - "integrity": "sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A==", - "dev": true - }, "colorette": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", @@ -2127,6 +2143,12 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "caniuse-lite": { + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -2246,9 +2268,9 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "is-fullwidth-code-point": { @@ -2832,9 +2854,9 @@ } }, "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", "dev": true }, "envinfo": { @@ -4221,12 +4243,12 @@ } }, "js2xmlparser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", - "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", "dev": true, "requires": { - "xmlcreate": "^2.0.3" + "xmlcreate": "^2.0.4" } }, "jsbn": { @@ -4236,25 +4258,26 @@ "dev": true }, "jsdoc": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", - "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", + "version": "3.6.10", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.10.tgz", + "integrity": "sha512-IdQ8ppSo5LKZ9o3M+LKIIK8i00DIe5msDvG3G81Km+1dhy0XrOWD0Ji8H61ElgyEj/O9KRLokgKbAM9XX9CJAg==", "dev": true, "requires": { "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.1", - "klaw": "^3.0.0", - "markdown-it": "^10.0.0", - "markdown-it-anchor": "^5.2.7", - "marked": "^2.0.3", + "js2xmlparser": "^4.0.2", + "klaw": "^4.0.1", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", "taffydb": "2.6.2", - "underscore": "~1.13.1" + "underscore": "~1.13.2" }, "dependencies": { "escape-string-regexp": { @@ -4317,12 +4340,6 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4349,15 +4366,23 @@ } }, "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" + }, + "dependencies": { + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + } } }, "jszlib": { @@ -4382,13 +4407,10 @@ "dev": true }, "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-4.0.1.tgz", + "integrity": "sha512-pgsE40/SvC7st04AHiISNewaIMUbY5V/K8b21ekiPiFoYs/EYSdsGa+FJArB1d441uq4Q8zZyIxvAzkGNlBdRw==", + "dev": true }, "klona": { "version": "2.0.4", @@ -4407,9 +4429,9 @@ } }, "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", "dev": true, "requires": { "uc.micro": "^1.0.1" @@ -4588,28 +4610,36 @@ } }, "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "dev": true, "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + } } }, "markdown-it-anchor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", - "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.4.1.tgz", + "integrity": "sha512-sLODeRetZ/61KkKLJElaU3NuU2z7MhXf12Ml1WJMSdwpngeofneCRF+JBbat8HiSqhniOMuTemXMrsI7hA6XyA==", "dev": true }, "marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.12.tgz", + "integrity": "sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==", "dev": true }, "mdurl": { @@ -4956,8 +4986,7 @@ "dependencies": { "ansi-regex": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "resolved": "", "dev": true }, "ansi-styles": { @@ -5805,9 +5834,9 @@ }, "dependencies": { "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -5815,12 +5844,6 @@ "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true } } }, @@ -6737,9 +6760,9 @@ "dev": true }, "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", + "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==", "dev": true }, "unicode-canonical-property-names-ecmascript": { @@ -7089,9 +7112,9 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -7200,9 +7223,9 @@ "dev": true }, "xmlcreate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", - "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "dev": true }, "y18n": { @@ -7234,8 +7257,7 @@ "dependencies": { "ansi-regex": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "resolved": "", "dev": true }, "is-fullwidth-code-point": { diff --git a/package.json b/package.json index dd3eb3da..9288bafe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.14.0-beta.3", + "version": "0.14.0-beta.4", "main": "dist/locuszoom.app.min.js", "module": "esm/index.js", "sideEffects": true, @@ -56,7 +56,7 @@ "eslint": "^7.32.0", "eslint-webpack-plugin": "^2.5.2", "friendly-errors-webpack-plugin": "^1.7.0", - "jsdoc": "^3.6.7", + "jsdoc": "^3.6.10", "jsdom": "^16.4.0", "jsdom-global": "^3.0.2", "mocha": "^8.3.0", From a2389b803bf2fbe888a92a1d8a91fef157bb4355 Mon Sep 17 00:00:00 2001 From: Andy Boughton Date: Wed, 16 Mar 2022 15:10:37 -0400 Subject: [PATCH 100/100] Add built assets for 0.14.0 (final) --- dist/ext/lz-aggregation-tests.min.js | 2 +- dist/ext/lz-credible-sets.min.js | 2 +- dist/ext/lz-dynamic-urls.min.js | 2 +- dist/ext/lz-forest-track.min.js | 2 +- dist/ext/lz-intervals-enrichment.min.js | 2 +- dist/ext/lz-intervals-track.min.js | 2 +- dist/ext/lz-parsers.min.js | 2 +- dist/ext/lz-tabix-source.min.js | 2 +- dist/ext/lz-widget-addons.min.js | 2 +- dist/locuszoom.app.min.js | 4 ++-- dist/locuszoom.app.min.js.map | 2 +- esm/version.js | 2 +- index.html | 4 ++-- package-lock.json | 8 +++++--- package.json | 2 +- 15 files changed, 21 insertions(+), 19 deletions(-) diff --git a/dist/ext/lz-aggregation-tests.min.js b/dist/ext/lz-aggregation-tests.min.js index 11cfbc06..d79132f6 100644 --- a/dist/ext/lz-aggregation-tests.min.js +++ b/dist/ext/lz-aggregation-tests.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.4 */ +/*! Locuszoom 0.14.0 */ var LzAggregationTests;(()=>{"use strict";var e={d:(t,r)=>{for(var s in r)e.o(r,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:r[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>o});const r=raremetal;function s(e){const t=e.Adapters.get("BaseLZAdapter");e.DataFunctions.add("gene_plus_aggregation",((e,[t,r])=>{const s={};return r.groups.forEach((function(e){Object.prototype.hasOwnProperty.call(s,e.group)||(s[e.group]=[]),s[e.group].push(e.pvalue)})),t.forEach((e=>{const t=e.gene_name,r=s[t];r&&(e.aggregation_best_pvalue=Math.min.apply(null,r))})),t})),e.Adapters.add("AggregationTestSourceLZ",class extends t{constructor(e){e.prefix_namespace=!1,super(e)}_buildRequestOptions(e){const{aggregation_tests:t={}}=e,{genoset_id:r=null,genoset_build:s=null,phenoset_build:o=null,pheno:n=null,calcs:a={},masks:u=[]}=t;return t.mask_ids=u.map((e=>e.name)),e.aggregation_tests=t,e}_getURL(e){return this._url}_getCacheKey(e){const{chr:t,start:r,end:s,aggregation_tests:o}=e,{genoset_id:n=null,genoset_build:a=null,phenoset_id:u=null,pheno:l=null,mask_ids:g}=o;return JSON.stringify({chrom:t,start:r,stop:s,genotypeDataset:n,phenotypeDataset:u,phenotype:l,samples:"ALL",genomeBuild:a,masks:g})}_performRequest(e){const t=this._getURL(e),r=this._getCacheKey(e);return fetch(t,{method:"POST",body:r,headers:{"Content-Type":"application/json"}}).then((e=>{if(!e.ok)throw new Error(e.statusText);return e.text()})).then((e=>{const t="string"==typeof e?JSON.parse(e):e;if(t.error)throw new Error(t.error);return t.data}))}_annotateRecords(e,t){const{aggregation_tests:s}=t,{calcs:o=[],mask_ids:n=[],masks:a=[]}=s;if(!e.groups)return{groups:[],variants:[]};e.groups=e.groups.filter((e=>"GENE"===e.groupType));const u=r.helpers.parsePortalJSON(e);let l=u[0];const g=u[1];if(l=l.byMask(n),!o||0===Object.keys(o).length)return{variants:[],groups:[],results:[]};return new r.helpers.PortalTestRunner(l,g,o).toJSON().then((function(e){const t=a.reduce(((e,t)=>(e[t.name]=t.description,e)),{});return e.data.groups.forEach((e=>{e.mask_name=t[e.mask]})),e.data})).catch((function(e){throw console.error(e),new Error("Failed to calculate aggregation test results")}))}}),e.Adapters.add("AssocFromAggregationLZ",class extends t{_buildRequestOptions(e,t){if(!t)throw new Error("Aggregation test results must be provided");return e._agg_results=t,e}_performRequest(e){return Promise.resolve(e._agg_results.variants)}_normalizeResponse(e){const t=new RegExp("(?:chr)?(.+):(\\d+)_?(\\w+)?/?([^_]+)?_?(.*)?");return e.map((e=>{const{variant:r,altFreq:s,pvalue:o}=e,n=r.match(t),[a,u,l,g]=n;return{variant:r,chromosome:u,position:+l,ref_allele:g,ref_allele_freq:1-s,log_pvalue:-Math.log10(o)}})).sort(((e,t)=>(e=e.variant)<(t=t.variant)?-1:e>t?1:0))}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(s);const o=s;LzAggregationTests=t.default})(); //# sourceMappingURL=lz-aggregation-tests.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-credible-sets.min.js b/dist/ext/lz-credible-sets.min.js index 48ce77b8..a29c5e2d 100644 --- a/dist/ext/lz-credible-sets.min.js +++ b/dist/ext/lz-credible-sets.min.js @@ -1,4 +1,4 @@ -/*! Locuszoom 0.14.0-beta.4 */ +/*! Locuszoom 0.14.0 */ var LzCredibleSets;(()=>{var e={803:function(e){var t;t=function(){return function(e){var t={};function r(o){if(t[o])return t[o].exports;var a=t[o]={i:o,l:!1,exports:{}};return e[o].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(e,t,r){"use strict"; /** * @module stats diff --git a/dist/ext/lz-dynamic-urls.min.js b/dist/ext/lz-dynamic-urls.min.js index 639996f1..8e54453a 100644 --- a/dist/ext/lz-dynamic-urls.min.js +++ b/dist/ext/lz-dynamic-urls.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.4 */ +/*! Locuszoom 0.14.0 */ var LzDynamicUrls;(()=>{"use strict";var t={d:(n,e)=>{for(var o in e)t.o(e,o)&&!t.o(n,o)&&Object.defineProperty(n,o,{enumerable:!0,get:e[o]})},o:(t,n)=>Object.prototype.hasOwnProperty.call(t,n)},n={};function e(t){const n={};if(t){const e=("?"===t[0]?t.substr(1):t).split("&");for(let t=0;ta});const a={paramsFromUrl:s,extractValues:o,plotUpdatesUrl:function(t,n,o){o=o||r;const c=function(c){const r=e(window.location.search),s=o(t,n,c),a=Object.assign({},r,s);if(Object.keys(a).some((function(t){return r[t]!=a[t]}))){const t=(i=a,`?${Object.keys(i).map((function(t){return`${encodeURIComponent(t)}=${encodeURIComponent(i[t])}`})).join("&")}`);Object.keys(r).length?history.pushState({},document.title,t):history.replaceState({},document.title,t)}var i};return t.on("state_changed",c),c},plotWatchesUrl:function(t,n,e){e=e||c;const o=function(o){const c=s(n);e(t,c)};return window.addEventListener("popstate",o),t.trackExternalListener(window,"popstate",o),o}};LzDynamicUrls=n.default})(); //# sourceMappingURL=lz-dynamic-urls.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-forest-track.min.js b/dist/ext/lz-forest-track.min.js index 7aeac1df..0f3e8cb6 100644 --- a/dist/ext/lz-forest-track.min.js +++ b/dist/ext/lz-forest-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.4 */ +/*! Locuszoom 0.14.0 */ var LzForestTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var i in a)t.o(a,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:a[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>s});const a=d3;function i(t){const e=t.DataLayers.get("BaseDataLayer"),i={point_size:40,point_shape:"square",color:"#888888",fill_opacity:1,y_axis:{axis:2},id_field:"id",confidence_intervals:{start_field:"ci_start",end_field:"ci_end"}};class s extends e{constructor(e){e=t.Layouts.merge(e,i),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),a=`y${this.layout.y_axis.axis}_scale`,i=this.parent[a](t.data[this.layout.y_axis.field]),s=this.resolveScalableParameter(this.layout.point_size,t.data),r=Math.sqrt(s/Math.PI);return{x_min:e-r,x_max:e+r,y_min:i-r,y_max:i+r}}render(){const t=this._applyFilters(),e=`y${this.layout.y_axis.axis}_scale`;if(this.layout.confidence_intervals&&this.layout.confidence_intervals.start_field&&this.layout.confidence_intervals.end_field){const a=this.svg.group.selectAll("rect.lz-data_layer-forest.lz-data_layer-forest-ci").data(t,(t=>t[this.layout.id_field])),i=t=>{let a=this.parent.x_scale(t[this.layout.confidence_intervals.start_field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`},s=t=>{const{start_field:e,end_field:a}=this.layout.confidence_intervals,i=this.parent.x_scale,s=i(t[a])-i(t[e]);return Math.max(s,1)},r=1;a.enter().append("rect").attr("class","lz-data_layer-forest lz-data_layer-forest-ci").attr("id",(t=>`${this.getElementId(t)}_ci`)).attr("transform",`translate(0, ${isNaN(this.parent.layout.height)?0:this.parent.layout.height})`).merge(a).attr("transform",i).attr("width",s).attr("height",r),a.exit().remove()}const i=this.svg.group.selectAll("path.lz-data_layer-forest.lz-data_layer-forest-point").data(t,(t=>t[this.layout.id_field])),s=isNaN(this.parent.layout.height)?0:this.parent.layout.height,r=a.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>{const i=this.resolveScalableParameter(this.layout.point_shape,t,e),s=`symbol${i.charAt(0).toUpperCase()+i.slice(1)}`;return a[s]||null}));i.enter().append("path").attr("class","lz-data_layer-forest lz-data_layer-forest-point").attr("id",(t=>this.getElementId(t))).attr("transform",`translate(0, ${s})`).merge(i).attr("transform",(t=>{let a=this.parent.x_scale(t[this.layout.x_axis.field]),i=this.parent[e](t[this.layout.y_axis.field]);return isNaN(a)&&(a=-1e3),isNaN(i)&&(i=-1e3),`translate(${a}, ${i})`})).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>{this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}}t.DataLayers.add("forest",s),t.DataLayers.add("category_forest",class extends s{_getDataExtent(t,e){const{confidence_intervals:i}=this.layout;if(i&&i.start_field&&i.end_field){const e=t=>+t[i.start_field],s=t=>+t[i.end_field];return[a.min(t,e),a.max(t,s)]}return super._getDataExtent(t,e)}getTicks(t,e){if(!["x","y1","y2"].includes(t))throw new Error(`Invalid dimension identifier ${t}`);if(t===`y${this.layout.y_axis.axis}`){const t=this.layout.y_axis.category_field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify category_field`);return this.data.map(((e,a)=>({y:a+1,text:e[t]})))}return[]}applyCustomDataMethods(){const t=this.layout.y_axis.field;if(!t)throw new Error(`Layout for ${this.layout.id} must specify yaxis.field`);return this.data=this.data.map(((e,a)=>(e[t]=a+1,e))),this.layout.y_axis.floor=0,this.layout.y_axis.ceiling=this.data.length+1,this}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(i);const s=i;LzForestTrack=e.default})(); //# sourceMappingURL=lz-forest-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-enrichment.min.js b/dist/ext/lz-intervals-enrichment.min.js index 78ba3895..702c0e8f 100644 --- a/dist/ext/lz-intervals-enrichment.min.js +++ b/dist/ext/lz-intervals-enrichment.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.4 */ +/*! Locuszoom 0.14.0 */ var LzIntervalsEnrichment;(()=>{"use strict";var e={d:(t,a)=>{for(var i in a)e.o(a,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:a[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>n});const a=Symbol.for("lzXCS"),i=Symbol.for("lzYCS"),s=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function r(e){const t={start_field:"start",end_field:"end",track_height:10,track_vertical_spacing:3,bounding_box_padding:2,color:"#B8B8B8",fill_opacity:.5,tooltip_positioning:"vertical"},r=e.DataLayers.get("BaseDataLayer");const n={namespace:{intervals:"intervals"},closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Tissue: {{intervals:tissueId|htmlescape}}
                                                                                                                                                                                                                                                                                \n Range: {{intervals:chromosome|htmlescape}}: {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}
                                                                                                                                                                                                                                                                                \n -log10 p: {{intervals:pValue|neglog10|scinotation|htmlescape}}
                                                                                                                                                                                                                                                                                \n Enrichment (n-fold): {{intervals:fold|scinotation|htmlescape}}"},o={id:"intervals_enrichment",type:"intervals_enrichment",tag:"intervals_enrichment",namespace:{intervals:"intervals"},match:{send:"intervals:tissueId"},id_field:"intervals:start",start_field:"intervals:start",end_field:"intervals:end",filters:[{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],y_axis:{axis:1,field:"intervals:fold",floor:0,upper_buffer:.1,min_extent:[0,10]},fill_opacity:.5,color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:n},c={id:"interval_matches",type:"highlight_regions",namespace:{intervals:"intervals"},match:{receive:"intervals:tissueId"},start_field:"intervals:start",end_field:"intervals:end",merge_field:"intervals:tissueId",filters:[{field:"lz_is_match",operator:"=",value:!0},{field:"intervals:ancestry",operator:"=",value:"EU"},{field:"intervals:pValue",operator:"<=",value:.05},{field:"intervals:fold",operator:">",value:2}],color:[{field:"intervals:tissueId",scale_function:"stable_choice",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],fill_opacity:.1},d={id:"intervals_enrichment",tag:"intervals_enrichment",min_height:250,height:250,margin:{top:35,right:50,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:34,tick_format:"region",extent:"state"},y1:{label:"enrichment (n-fold)",label_offset:40}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[o]},h={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:e.Layouts.get("toolbar","standard_association"),panels:[function(){const t=e.Layouts.get("panel","association");return t.data_layers.unshift(c),t}(),d,e.Layouts.get("panel","genes")]};e.DataLayers.add("intervals_enrichment",class extends r{constructor(a){e.Layouts.merge(a,t),super(...arguments)}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}render(){const e=this._applyFilters(this.data),{start_field:t,end_field:r,bounding_box_padding:n,track_height:o}=this.layout,c=this.layout.y_axis.field,d=`y${this.layout.y_axis.axis}_scale`,{x_scale:h,[d]:f}=this.parent;e.forEach((e=>{e[a]=h(e[t]),e[s]=h(e[r]),e[i]=f(e[c])-this.getTrackHeight()/2+n,e[l]=e[i]+o})),e.sort(((e,t)=>{const i=e[s]-e[a];return t[s]-t[a]-i}));const _=this.svg.group.selectAll("rect").data(e);_.enter().append("rect").merge(_).attr("id",(e=>this.getElementId(e))).attr("x",(e=>e[a])).attr("y",(e=>e[i])).attr("width",(e=>e[s]-e[a])).attr("height",this.layout.track_height).attr("fill",((e,t)=>this.resolveScalableParameter(this.layout.color,e,t))).attr("fill-opacity",((e,t)=>this.resolveScalableParameter(this.layout.fill_opacity,e,t))),_.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this))}_getTooltipPosition(e){return{x_min:e.data[a],x_max:e.data[s],y_min:e.data[i],y_max:e.data[l]}}}),e.Layouts.add("tooltip","intervals_enrichment",n),e.Layouts.add("data_layer","intervals_enrichment",o),e.Layouts.add("panel","intervals_enrichment",d),e.Layouts.add("plot","intervals_association_enrichment",h)}"undefined"!=typeof LocusZoom&&LocusZoom.use(r);const n=r;LzIntervalsEnrichment=t.default})(); //# sourceMappingURL=lz-intervals-enrichment.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-intervals-track.min.js b/dist/ext/lz-intervals-track.min.js index 63ea77d9..708f1028 100644 --- a/dist/ext/lz-intervals-track.min.js +++ b/dist/ext/lz-intervals-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.4 */ +/*! Locuszoom 0.14.0 */ var LzIntervalsTrack;(()=>{"use strict";var t={d:(e,a)=>{for(var r in a)t.o(a,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>o});const a=d3,r=Symbol.for("lzXCS"),s=Symbol.for("lzYCS"),i=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function n(t){const e=t.Adapters.get("BaseUMAdapter"),n=t.Widgets.get("_Button"),o=t.Widgets.get("BaseWidget");function c(t,e){return e?`rgb(${e})`:null}const d={start_field:"start",end_field:"end",track_label_field:"state_name",track_split_field:"state_id",track_split_order:"DESC",track_split_legend_to_y_axis:2,split_tracks:!0,track_height:15,track_vertical_spacing:3,bounding_box_padding:2,always_hide_legend:!1,color:"#B8B8B8",fill_opacity:1,tooltip_positioning:"vertical"},g=t.DataLayers.get("BaseDataLayer");const _={closable:!1,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"{{intervals:state_name|htmlescape}}
                                                                                                                                                                                                                                                                                {{intervals:start|htmlescape}}-{{intervals:end|htmlescape}}"},h={namespace:{intervals:"intervals"},id:"intervals",type:"intervals",tag:"intervals",id_field:"{{intervals:start}}_{{intervals:end}}_{{intervals:state_name}}",start_field:"intervals:start",end_field:"intervals:end",track_split_field:"intervals:state_name",track_label_field:"intervals:state_name",split_tracks:!1,always_hide_legend:!0,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:state_name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],legend:[],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}],onshiftclick:[{action:"toggle",status:"selected"}]},tooltip:_},u=t.Layouts.merge({id_field:"{{intervals:chromStart}}_{{intervals:chromEnd}}_{{intervals:name}}",start_field:"intervals:chromStart",end_field:"intervals:chromEnd",track_split_field:"intervals:name",track_label_field:"intervals:name",split_tracks:!0,always_hide_legend:!1,color:[{field:"intervals:itemRgb",scale_function:"to_rgb"},{field:"intervals:name",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],tooltip:t.Layouts.merge({html:"Group: {{intervals:name|htmlescape}}
                                                                                                                                                                                                                                                                                \nRegion: {{intervals:chromStart|htmlescape}}-{{intervals:chromEnd|htmlescape}}\n{{#if intervals:score}}
                                                                                                                                                                                                                                                                                \nScore: {{intervals:score|htmlescape}}{{/if}}"},_)},h),p={id:"intervals",tag:"intervals",min_height:50,height:50,margin:{top:25,right:150,bottom:5,left:70},toolbar:function(){const e=t.Layouts.get("toolbar","standard_panel");return e.widgets.push({type:"toggle_split_tracks",data_layer_id:"intervals",position:"right"}),e}(),axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},legend:{hidden:!0,orientation:"horizontal",origin:{x:50,y:0},pad_from_bottom:5},data_layers:[h]},y=t.Layouts.merge({min_height:120,height:120,data_layers:[u]},p),b={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:t.Layouts.get("toolbar","standard_association"),panels:[t.Layouts.get("panel","association"),t.Layouts.merge({min_height:120,height:120},p),t.Layouts.get("panel","genes")]};t.Adapters.add("IntervalLZ",class extends e{_getURL(t){const e=`?filter=id in ${this._config.source} and chromosome eq '${t.chr}' and start le ${t.end} and end ge ${t.start}`;return`${super._getURL(t)}${e}`}}),t.DataLayers.add("intervals",class extends g{constructor(e){t.Layouts.merge(e,d),super(...arguments),this._previous_categories=[],this._categories=[]}initialize(){super.initialize(),this._statusnodes_group=this.svg.group.append("g").attr("class","lz-data-layer-intervals lz-data-layer-intervals-statusnode"),this._datanodes_group=this.svg.group.append("g").attr("class","lz-data_layer-intervals")}_arrangeTrackSplit(t){const{track_split_field:e}=this.layout,a={};return t.forEach((t=>{const r=t[e];Object.prototype.hasOwnProperty.call(a,r)||(a[r]=[]),a[r].push(t)})),a}_arrangeTracksLinear(t,e=!0){if(e)return[t];const{start_field:a,end_field:r}=this.layout,s=[[]];return t.forEach(((t,e)=>{for(let e=0;e{d[t].forEach((t=>{t[r]=e(t[a]),t[i]=e(t[n]),t[s]=g*this.getTrackHeight()+o,t[l]=t[s]+c,t.track=g}))})),[g,Object.values(d).reduce(((t,e)=>t.concat(e)),[])]}getElementStatusNodeId(t){if(this.layout.split_tracks){const e="object"==typeof t?t.track:t;return`${this.getBaseId()}-statusnode-${e}`.replace(/[^\w]/g,"_")}return null}getTrackHeight(){return this.layout.track_height+this.layout.track_vertical_spacing+2*this.layout.bounding_box_padding}_applyLayoutOptions(){const t=this,e=this._base_layout,a=this.layout,r=e.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function})),s=a.color.find((function(t){return t.scale_function&&"categorical_bin"===t.scale_function}));if(!r)throw new Error("Interval tracks must define a `categorical_bin` color scale");const i=r.parameters.categories.length&&r.parameters.values.length,l=e.legend&&e.legend.length;if(!!i^!!l)throw new Error("To use a manually specified color scheme, both color and legend options must be set.");const n=e.color.find((function(t){return t.scale_function&&"to_rgb"===t.scale_function})),o=n&&n.field,c=this._generateCategoriesFromData(this.data,o);if(!i&&!l){const e=this._makeColorScheme(c);s.parameters.categories=c.map((function(t){return t[0]})),s.parameters.values=e,this.layout.legend=c.map((function(e,a){const r=e[0],i={shape:"rect",width:9,label:e[1],color:s.parameters.values[a]};return i[t.layout.track_split_field]=r,i}))}}render(){this._applyLayoutOptions(),this._previous_categories=this._categories;const[t,e]=this._assignTracks(this.data);this._categories=t;if(!t.every(((t,e)=>t===this._previous_categories[e])))return void this.updateSplitTrackAxis(t);const l=this._applyFilters(e);this._statusnodes_group.selectAll("rect").remove();const n=this._statusnodes_group.selectAll("rect").data(a.range(t.length));if(this.layout.split_tracks){const t=this.getTrackHeight();n.enter().append("rect").attr("class","lz-data_layer-intervals lz-data_layer-intervals-statusnode lz-data_layer-intervals-shared").attr("rx",this.layout.bounding_box_padding).attr("ry",this.layout.bounding_box_padding).merge(n).attr("id",(t=>this.getElementStatusNodeId(t))).attr("x",0).attr("y",(e=>e*t)).attr("width",this.parent.layout.cliparea.width).attr("height",Math.max(t-this.layout.track_vertical_spacing,1))}n.exit().remove();const o=this._datanodes_group.selectAll("rect").data(l,(t=>t[this.layout.id_field]));o.enter().append("rect").merge(o).attr("id",(t=>this.getElementId(t))).attr("x",(t=>t[r])).attr("y",(t=>t[s])).attr("width",(t=>Math.max(t[i]-t[r],1))).attr("height",this.layout.track_height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),o.exit().remove(),this._datanodes_group.call(this.applyBehaviors.bind(this)),this.parent&&this.parent.legend&&this.parent.legend.render()}_getTooltipPosition(t){return{x_min:t.data[r],x_max:t.data[i],y_min:t.data[s],y_max:t.data[l]}}updateSplitTrackAxis(t){const e=!!this.layout.track_split_legend_to_y_axis&&`y${this.layout.track_split_legend_to_y_axis}`;if(this.layout.split_tracks){const a=+t.length||0,r=+this.layout.track_height||0,s=2*(+this.layout.bounding_box_padding||0)+(+this.layout.track_vertical_spacing||0),i=a*r+(a-1)*s;this.parent.scaleHeightToData(i),e&&this.parent.legend&&(this.parent.legend.hide(),this.parent.layout.axes[e]={render:!0,ticks:[],range:{start:i-this.layout.track_height/2,end:this.layout.track_height/2}},this.layout.legend.forEach((r=>{const s=r[this.layout.track_split_field];let i=t.findIndex((t=>t===s));-1!==i&&("DESC"===this.layout.track_split_order&&(i=Math.abs(i-a-1)),this.parent.layout.axes[e].ticks.push({y:i-1,text:r.label}))})),this.layout.y_axis={axis:this.layout.track_split_legend_to_y_axis,floor:1,ceiling:a}),this.parent_plot.positionPanels()}else e&&this.parent.legend&&(this.layout.always_hide_legend||this.parent.legend.show(),this.parent.layout.axes[e]={render:!1},this.parent.render());return this}toggleSplitTracks(){return this.layout.split_tracks=!this.layout.split_tracks,this.parent.legend&&!this.layout.always_hide_legend&&(this.parent.layout.margin.bottom=5+(this.layout.split_tracks?0:this.parent.legend.layout.height+5)),this.render(),this}_makeColorScheme(t){if(t.find((t=>t[2])))return t.map((t=>c(0,t[2])));const e=t.length;return e<=15?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(233,150,122)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(50,205,50)","rgb(255,69,0)","rgb(255,0,0)"]:e<=18?["rgb(212,212,212)","rgb(192,192,192)","rgb(128,128,128)","rgb(189,183,107)","rgb(205,92,92)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,100,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]:["rgb(212,212,212)","rgb(128,128,128)","rgb(112,48,160)","rgb(230,184,183)","rgb(138,145,208)","rgb(102,205,170)","rgb(255,255,102)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,255,0)","rgb(255,195,77)","rgb(255,195,77)","rgb(255,195,77)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(194,225,5)","rgb(0,150,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(0,128,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,69,0)","rgb(255,0,0)"]}_generateCategoriesFromData(t,e){const a=this,r=this._base_layout.legend;if(r&&r.length)return r.map((t=>[t[this.layout.track_split_field],t.label,t.color]));const s={},i=[];return t.forEach((t=>{const r=t[a.layout.track_split_field];Object.prototype.hasOwnProperty.call(s,r)||(s[r]=null,i.push([r,t[this.layout.track_label_field],t[e]]))})),i}}),t.Layouts.add("tooltip","standard_intervals",_),t.Layouts.add("data_layer","intervals",h),t.Layouts.add("data_layer","bed_intervals",u),t.Layouts.add("panel","intervals",p),t.Layouts.add("panel","bed_intervals",y),t.Layouts.add("plot","interval_association",b),t.ScaleFunctions.add("to_rgb",c),t.Widgets.add("toggle_split_tracks",class extends o{constructor(t){if(super(...arguments),t.data_layer_id||(t.data_layer_id="intervals"),!this.parent_panel.data_layers[t.data_layer_id])throw new Error("Toggle split tracks widget specifies an invalid data layer ID")}update(){const t=this.parent_panel.data_layers[this.layout.data_layer_id],e=t.layout.split_tracks?"Merge Tracks":"Split Tracks";return this.button?(this.button.setHtml(e),this.button.show(),this.parent.position(),this):(this.button=new n(this).setColor(this.layout.color).setHtml(e).setTitle("Toggle whether tracks are split apart or merged together").setOnclick((()=>{t.toggleSplitTracks(),this.scale_timeout&&clearTimeout(this.scale_timeout),this.scale_timeout=setTimeout((()=>{this.parent_panel.scaleHeightToData(),this.parent_plot.positionPanels()}),0),this.update()})),this.update())}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const o=n;LzIntervalsTrack=e.default})(); //# sourceMappingURL=lz-intervals-track.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-parsers.min.js b/dist/ext/lz-parsers.min.js index 309d1025..3e6b48dd 100644 --- a/dist/ext/lz-parsers.min.js +++ b/dist/ext/lz-parsers.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.4 */ +/*! Locuszoom 0.14.0 */ var LzParsers;(()=>{"use strict";var e={d:(r,t)=>{for(var l in t)e.o(t,l)&&!e.o(r,l)&&Object.defineProperty(r,l,{enumerable:!0,get:t[l]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r)},r={};e.d(r,{default:()=>d});const t=new Set(["",".","NA","N/A","n/a","nan","-nan","NaN","-NaN","null","NULL","None",null]),l=/([\d.-]+)([\sxeE]*)([0-9-]*)/;function n(e){return Number.isInteger(e)}function o(e,r=t,l=null){return e.map((e=>r.has(e)?l:e))}function a(e){return e.replace(/^chr/g,"").toUpperCase()}function s(e,r=!1){if(null===e)return e;const t=+e;if(r)return t;if(t<0||t>1)throw new Error("p value is not in the allowed range");if(0===t){if("0"===e)return 1/0;let[,r,,t]=e.match(l);return r=+r,t=""!==t?+t:0,0===r?1/0:-(Math.log10(+r)+ +t)}return-Math.log10(t)}function u(e){return null==e||"."===e?null:e}function i(e){return(e=u(e))?+e:null}const c=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function f(e,r=!1){const t=e&&e.match(c);if(t)return t.slice(1);if(r)return null;throw new Error(`Could not understand marker format for ${e}. Should be of format chr:pos or chr:pos_ref/alt`)}function p(e){const r=f(e);if(!r)throw new Error(`Unable to normalize marker format for variant: ${e}`);const[t,l,n,o]=r;let a=`${t}:${l}`;return n&&o&&(a+=`_${n}/${o}`),a}function _(e,r){const t=Array(r.length+1).fill(null).map((()=>Array(e.length+1).fill(null)));for(let r=0;r<=e.length;r+=1)t[0][r]=r;for(let e=0;e<=r.length;e+=1)t[e][0]=e;for(let l=1;l<=r.length;l+=1)for(let n=1;n<=e.length;n+=1){const o=e[n-1]===r[l-1]?0:1;t[l][n]=Math.min(t[l][n-1]+1,t[l-1][n]+1,t[l-1][n-1]+o)}return t[r.length][e.length]}function m(e,r,t=2){let l=t+1,n=null;for(let t=0;t_(o,e))));ae.variant1===r.ld_refvar))}}e.Adapters.add("UserTabixLD",l)}}"undefined"!=typeof LocusZoom&&LocusZoom.use(h);const d={install:h,makeBed12Parser:function({normalize:e=!0}={}){return r=>{const t=r.trim().split("\t");let[l,n,o,s,c,f,p,_,m,h,d,v]=t;if(!(l&&n&&o))throw new Error("Sample data must provide all required BED columns");if(f=u(f),e&&(l=a(l),n=+n+1,o=+o,c=i(c),p=i(p),_=i(_),m=u(m),h=i(h),d=u(d),d=d?d.replace(/,$/,"").split(",").map((e=>+e)):null,v=u(v),v=v?v.replace(/,$/,"").split(",").map((e=>+e+1)):null,d&&v&&h&&(d.length!==h||v.length!==h)))throw new Error("Block size and start information should provide the same number of items as in blockCount");return{chrom:l,chromStart:n,chromEnd:o,name:s,score:c,strand:f,thickStart:p,thickEnd:_,itemRgb:m,blockCount:h,blockSizes:d,blockStarts:v}}},makeGWASParser:function({marker_col:e,chrom_col:r,pos_col:l,ref_col:o,alt_col:u,pvalue_col:i,is_neg_log_pvalue:c=!1,rsid_col:p,beta_col:_,stderr_beta_col:m,allele_freq_col:h,allele_count_col:d,n_samples_col:v,is_alt_effect:g=!0,delimiter:w="\t"}){if(n(e)&&n(r)&&n(l))throw new Error("Must specify either marker OR chr + pos");if(!(n(e)||n(r)&&n(l)))throw new Error("Must specify how to locate marker");if(n(d)&&n(h))throw new Error("Allele count and frequency options are mutually exclusive");if(n(d)&&!n(v))throw new Error("To calculate allele frequency from counts, you must also provide n_samples");return b=>{const k=b.split(w);let y,E,q,A,N,$,S,L=null,z=null,P=null,C=null;if(n(e))[y,E,q,A]=f(k[e-1],!1);else{if(!n(r)||!n(l))throw new Error("Must specify all fields required to identify the variant");y=k[r-1],E=k[l-1]}if(y=a(y),y.startsWith("RS"))throw new Error(`Invalid chromosome specified: value "${y}" is an rsID`);n(o)&&(q=k[o-1]),n(u)&&(A=k[u-1]),n(p)&&(L=k[p-1]),t.has(q)&&(q=null),t.has(A)&&(A=null),t.has(L)?L=null:L&&(L=L.toLowerCase(),L.startsWith("rs")||(L=`rs${L}`));const O=s(k[i-1],c);q=q||null,A=A||null,n(h)&&(N=k[h-1]),n(d)&&($=k[d-1],S=k[v-1]),n(_)&&(z=k[_-1],z=t.has(z)?null:+z),n(m)&&(P=k[m-1],P=t.has(P)?null:+P),(h||d)&&(C=function({freq:e,allele_count:r,n_samples:l,is_alt_effect:n=!0}){if(void 0!==e&&void 0!==r)throw new Error("Frequency and allele count options are mutually exclusive");let o;if(void 0===e&&(t.has(r)||t.has(l)))return null;if(void 0===e&&void 0!==r)o=+r/+l/2;else{if(t.has(e))return null;o=+e}if(o<0||o>1)throw new Error("Allele frequency is not in the allowed range");return n?o:1-o}({freq:N,allele_count:$,n_samples:S,is_alt_effect:g}));const R=q&&A?`_${q}/${A}`:"";return{chromosome:y,position:+E,ref_allele:q?q.toUpperCase():null,alt_allele:A?A.toUpperCase():null,variant:`${y}:${E}${R}`,rsid:L,log_pvalue:O,beta:z,stderr_beta:P,alt_allele_freq:C}}},guessGWAS:function(e,r,t=1){const l=e.map((e=>e?e.toLowerCase():e));l[0].replace("/^#+/","");const n=function(e,r){let t;const l=(e,r,l)=>{const n=o(r.map((r=>r[e])));try{t=n.map((e=>s(e,l)))}catch(e){return!1}return t.every((e=>!Number.isNaN(e)))},n=m(["neg_log_pvalue","log_pvalue","log_pval","logpvalue"],e),a=m(["pvalue","p.value","p-value","pval","p_score","p","p_value"],e);return null!==n&&l(n,r,!0)?{pvalue_col:n+1,is_neg_log_pvalue:!0}:a&&l(a,r,!1)?{pvalue_col:a+1,is_neg_log_pvalue:!1}:null}(l,r);if(!n)return null;l[n.pvalue_col-1]=null;const a=function(e,r){const t=r[0];let l=m(["snpid","marker","markerid","snpmarker","chr:position"],e);if(null!==l&&f(t[l],!0))return l+=1,{marker_col:l};const n=e.slice(),o=[["chrom_col",["chrom","chr","chromosome"],!0],["pos_col",["position","pos","begin","beg","bp","end","ps","base_pair_location"],!0],["ref_col",["A1","ref","reference","allele0","allele1"],!1],["alt_col",["A2","alt","alternate","allele1","allele2"],!1]],a={};for(let e=0;e{l[a[e]]=null}));const u=function(e,r){function t(e,r){const t=o(r.map((r=>r[e])));let l;try{l=t.filter((e=>null!==e)).map((e=>+e))}catch(e){return!1}return l.every((e=>!Number.isNaN(e)))}const l=m(["beta","effect_size","alt_effsize","effect"],e,0),n=m(["stderr_beta","stderr","sebeta","effect_size_sd","se","standard_error"],e,0),a={};return null!==l&&t(l,r)&&(a.beta_col=l+1),null!==n&&t(n,r)&&(a.stderr_beta_col=n+1),a}(l,r);return n&&a?Object.assign({},n,a,u||{}):null},makePlinkLdParser:function({normalize:e=!0}={}){return r=>{let[t,l,n,o,s,u,i]=r.trim().split("\t");return e&&(t=a(t),o=a(o),n=p(n),u=p(u),l=+l,s=+s,i=+i),{chromosome1:t,position1:l,variant1:n,chromosome2:o,position2:s,variant2:u,correlation:i}}}};LzParsers=r.default})(); //# sourceMappingURL=lz-parsers.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-tabix-source.min.js b/dist/ext/lz-tabix-source.min.js index 54314cf3..60aea16c 100644 --- a/dist/ext/lz-tabix-source.min.js +++ b/dist/ext/lz-tabix-source.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.4 */ +/*! Locuszoom 0.14.0 */ var LzTabix;(()=>{var t={398:t=>{var i=15,e=-2,n=-3,r=-5,s=13,a=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],o=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],h=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],l=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],f=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],u=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],d=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];function _(){}function c(){this.was=[0]}_.prototype.inflateInit=function(t,i){return t||(t=15),i&&(i=!1),this.istate=new c,this.istate.inflateInit(this,i?-t:t)},_.prototype.inflate=function(t){return null==this.istate?e:this.istate.inflate(this,t)},_.prototype.inflateEnd=function(){if(null==this.istate)return e;var t=istate.inflateEnd(this);return this.istate=null,t},_.prototype.inflateSync=function(){return istate.inflateSync(this)},_.prototype.inflateSetDictionary=function(t,i){return istate.inflateSetDictionary(this,t,i)},c.prototype.inflateReset=function(t){return null==t||null==t.istate?e:(t.total_in=t.total_out=0,t.msg=null,t.istate.mode=0!=t.istate.nowrap?7:0,t.istate.blocks.reset(t,null),0)},c.prototype.inflateEnd=function(t){return null!=this.blocks&&this.blocks.free(t),this.blocks=null,0},c.prototype.inflateInit=function(t,i){return t.msg=null,this.blocks=null,nowrap=0,i<0&&(i=-i,nowrap=1),i<8||i>15?(this.inflateEnd(t),e):(this.wbits=i,t.istate.blocks=new v(t,0!=t.istate.nowrap?null:this,1<>4)>t.istate.wbits){t.istate.mode=s,t.msg="invalid window size",t.istate.marker=5;break}t.istate.mode=1;case 1:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,o=255&t.next_in[t.next_in_index++],((t.istate.method<<8)+o)%31!=0){t.istate.mode=s,t.msg="incorrect header check",t.istate.marker=5;break}if(0==(32&o)){t.istate.mode=7;break}t.istate.mode=2;case 2:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=3;case 3:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=4;case 4:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=5;case 5:return 0==t.avail_in?a:(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.adler=t.istate.need,t.istate.mode=6,2);case 6:return t.istate.mode=s,t.msg="need dictionary",t.istate.marker=0,e;case 7:if((a=t.istate.blocks.proc(t,a))==n){t.istate.mode=s,t.istate.marker=0;break}if(0==a&&(a=i),1!=a)return a;if(a=i,t.istate.blocks.reset(t,t.istate.was),0!=t.istate.nowrap){t.istate.mode=12;break}t.istate.mode=8;case 8:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need=(255&t.next_in[t.next_in_index++])<<24&4278190080,t.istate.mode=9;case 9:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<16&16711680,t.istate.mode=10;case 10:if(0==t.avail_in)return a;a=i,t.avail_in--,t.total_in++,t.istate.need+=(255&t.next_in[t.next_in_index++])<<8&65280,t.istate.mode=11;case 11:if(0==t.avail_in)return a;if(a=i,t.avail_in--,t.total_in++,t.istate.need+=255&t.next_in[t.next_in_index++],t.istate.was[0]!=t.istate.need){t.istate.mode=s,t.msg="incorrect data check",t.istate.marker=5;break}t.istate.mode=12;case 12:return 1;case s:return n;default:return e}},c.prototype.inflateSetDictionary=function(t,i,r){var s=0,a=r;return null==t||null==t.istate||6!=t.istate.mode?e:t._adler.adler32(1,i,0,r)!=t.adler?n:(t.adler=t._adler.adler32(0,null,0,0),a>=1<>>1){case 0:o>>>=3,o>>>=r=7&(h-=3),h-=r,this.mode=1;break;case 1:w(v=new Int32Array(1),p=new Int32Array(1),m=[],y=[],t),this.codes.init(v[0],p[0],m[0],0,y[0],0,t),o>>>=3,h-=3,this.mode=6;break;case 2:o>>>=3,h-=3,this.mode=3;break;case 3:return o>>>=3,h-=3,this.mode=s,t.msg="invalid block type",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i)}break;case 1:for(;h<32;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>16&65535)!=(65535&o))return this.mode=s,t.msg="invalid stored block lengths",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.left=65535&o,o=h=0,this.mode=0!=this.left?2:0!=this.last?7:0;break;case 2:if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);if(0==d&&(u==end&&0!=read&&(d=(u=0)f&&(r=f),r>d&&(r=d),g(t.next_in,l,this.window,u,r),l+=r,f-=r,u+=r,d-=r,0!=(this.left-=r))break;this.mode=0!=this.last?7:0;break;case 3:for(;h<14;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<29||(r>>5&31)>29)return this.mode=9,t.msg="too many length or distance symbols",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);if(r=258+(31&r)+(r>>5&31),null==this.blens||this.blens.length>>=14,h-=14,this.index=0,mode=4;case 4:for(;this.index<4+(this.table>>>10);){for(;h<3;){if(0==f)return this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);i=0,f--,o|=(255&t.next_in[l++])<>>=3,h-=3}for(;this.index<19;)this.blens[b[this.index++]]=0;if(this.bb[0]=7,0!=(r=this.inftree.inflate_trees_bits(this.blens,this.bb,this.tb,this.hufts,t)))return(i=r)==n&&(this.blens=null,this.mode=9),this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,write=u,this.inflate_flush(t,i);this.index=0,this.mode=5;case 5:for(;r=this.table,this.index<258+(31&r)+(r>>5&31);){var c,x;for(r=this.bb[0];h>>=r,h-=r,this.blens[this.index++]=x;else{for(_=18==x?7:x-14,c=18==x?11:3;h>>=r)&a[_],o>>>=_,h-=_,(_=this.index)+c>258+(31&(r=this.table))+(r>>5&31)||16==x&&_<1)return this.blens=null,this.mode=9,t.msg="invalid bit length repeat",i=n,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);x=16==x?this.blens[_-1]:0;do{this.blens[_++]=x}while(0!=--c);this.index=_}}this.tb[0]=-1;var v=new Int32Array(1),p=new Int32Array(1),m=new Int32Array(1),y=new Int32Array(1);if(v[0]=9,p[0]=6,r=this.table,0!=(r=this.inftree.inflate_trees_dynamic(257+(31&r),1+(r>>5&31),this.blens,v,p,m,y,this.hufts,t)))return r==n&&(this.blens=null,this.mode=s),i=r,this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,this.inflate_flush(t,i);this.codes.init(v[0],p[0],this.hufts,m[0],this.hufts,y[0],t),this.mode=6;case 6:if(this.bitb=o,this.bitk=h,t.avail_in=f,t.total_in+=l-t.next_in_index,t.next_in_index=l,this.write=u,1!=(i=this.codes.proc(this,t,i)))return this.inflate_flush(t,i);if(i=0,this.codes.free(t),l=t.next_in_index,f=t.avail_in,o=this.bitb,h=this.bitk,d=(u=this.write)t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,(s+=e)==this.end&&(s=0,this.write==this.end&&(this.write=0),(e=this.write-s)>t.avail_out&&(e=t.avail_out),0!=e&&i==r&&(i=0),t.avail_out-=e,t.total_out+=e,null!=this.checkfn&&(t.adler=this.check=t._adler.adler32(this.check,this.window,s,e)),g(this.window,s,t.next_out,n,e),n+=e,s+=e),t.next_out_index=n,this.read=s,i};function p(){}function m(){}function w(t,i,e,n,r){return t[0]=9,i[0]=5,e[0]=o,n[0]=h,0}p.prototype.init=function(t,i,e,n,r,s,a){this.mode=0,this.lbits=t,this.dbits=i,this.ltree=e,this.ltree_index=n,this.dtree=r,this.dtree_index=s,this.tree=null},p.prototype.proc=function(t,i,r){var s,o,h,l,f,u,d,_=0,c=0,x=0;for(x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)=258&&l>=10&&(t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,r=this.inflate_fast(this.lbits,this.dbits,this.ltree,this.ltree_index,this.dtree,this.dtree_index,t,i),x=i.next_in_index,l=i.avail_in,_=t.bitb,c=t.bitk,u=(f=t.write)>>=this.tree[o+1],c-=this.tree[o+1],0==(h=this.tree[o])){this.lit=this.tree[o+2],this.mode=6;break}if(0!=(16&h)){this.get=15&h,this.len=this.tree[o+2],this.mode=2;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}if(0!=(32&h)){this.mode=7;break}return this.mode=9,i.msg="invalid literal/length code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 2:for(s=this.get;c>=s,c-=s,this.need=this.dbits,this.tree=this.dtree,this.tree_index=this.dtree_index,this.mode=3;case 3:for(s=this.need;c>=this.tree[o+1],c-=this.tree[o+1],0!=(16&(h=this.tree[o]))){this.get=15&h,this.dist=this.tree[o+2],this.mode=4;break}if(0==(64&h)){this.need=h,this.tree_index=o/3+this.tree[o+2];break}return this.mode=9,i.msg="invalid distance code",r=n,t.bitb=_,t.bitk=c,i.avail_in=l,i.total_in+=x-i.next_in_index,i.next_in_index=x,t.write=f,t.inflate_flush(i,r);case 4:for(s=this.get;c>=s,c-=s,this.mode=5;case 5:for(d=f-this.dist;d<0;)d+=t.end;for(;0!=this.len;){if(0==u&&(f==t.end&&0!=t.read&&(u=(f=0)7&&(c-=8,l++,x--),t.write=f,r=t.inflate_flush(i,r),u=(f=t.write)>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15,k=u[S+2]+(c&a[_]),c>>=_,x-=_;x<15;)v--,c|=(255&l.next_in[b++])<>=u[S+1],x-=u[S+1],0!=(16&_)){for(_&=15;x<_;)v--,c|=(255&l.next_in[b++])<>=_,x-=_,m-=k,p>=A)C=p-A,h.window[p++]=h.window[C++],h.window[p++]=h.window[C++],k-=2;else{C=p-A;do{C+=h.end}while(C<0);if(k>(_=h.end-C)){if(k-=_,p-C>0&&_>p-C)do{h.window[p++]=h.window[C++]}while(0!=--_);else g(h.window,C,h.window,p,_),p+=_,C+=_,_=0;C=0}}do{h.window[p++]=h.window[C++]}while(0!=--k);break}if(0!=(64&_))return l.msg="invalid distance code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n;f+=u[S+2],_=u[S=3*(d+(f+=c&a[_]))]}break}if(0!=(64&_))return 0!=(32&_)?(v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,1):(l.msg="invalid literal/length code",v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,n);if(f+=u[S+2],0==(_=u[S=3*(d+(f+=c&a[_]))])){c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--;break}}else c>>=u[S+1],x-=u[S+1],h.window[p++]=u[S+2],m--}while(m>=258&&v>=10);return v+=k=x>>3<(k=l.avail_in-v)?x>>3:k,b-=k,x-=k<<3,h.bitb=c,h.bitk=x,l.avail_in=v,l.total_in+=b-l.next_in_index,l.next_in_index=b,h.write=p,0},m.prototype.huft_build=function(t,e,s,a,o,h,l,f,u,d,_){var c,x,b,v,p,m,w,y,k,A,C,S,R,I,L;A=0,p=s;do{this.c[t[e+A]]++,A++,p--}while(0!=p);if(this.c[0]==s)return l[0]=-1,f[0]=0,0;for(y=f[0],m=1;m<=i&&0==this.c[m];m++);for(w=m,yp&&(y=p),f[0]=y,I=1<S+y;){if(v++,L=(L=b-(S+=y))>y?y:L,(x=1<<(m=w-S))>c+1&&(x-=c+1,R=w,m1440)return n;this.u[v]=C=this.hn[0],this.hn[0]+=L,0!=v?(this.x[v]=p,this.r[0]=m,this.r[1]=y,m=p>>>S-y,this.r[2]=C-this.u[v-1]-m,g(this.r,0,u,3*(this.u[v-1]+m),3)):l[0]=C}for(this.r[1]=w-S,A>=s?this.r[0]=192:_[A]>>S;m>>=1)p^=m;for(p^=m,k=(1<257?(x==n?c.msg="oversubscribed distance tree":x==r?(c.msg="incomplete distance tree",x=n):-4!=x&&(c.msg="empty distance tree with lengths",x=n),x):0)},m.prototype.initWorkArea=function(t){null==this.hn&&(this.hn=new Int32Array(1),this.v=new Int32Array(t),this.c=new Int32Array(16),this.r=new Int32Array(3),this.u=new Int32Array(i),this.x=new Int32Array(16)),this.v.length100?k(new Uint8Array(t.buffer,t.byteOffset+i,r),e,n):function(t,i,e,n,r){for(var s=0;s{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},e.o=(t,i)=>Object.prototype.hasOwnProperty.call(t,i);var n={};(()=>{"use strict";e.d(n,{default:()=>q});function t(t){return r(i(s(t)))}function i(t){return o(h(a(t),8*t.length))}function r(t){for(var i="",e=t.length,n=0;n8*t.length?i+="":i+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(r>>>6*(3-s)&63);return i}function s(t){for(var i,e,n="",r=-1;++r>>6&31,128|63&i):i<=65535?n+=String.fromCharCode(224|i>>>12&15,128|i>>>6&63,128|63&i):i<=2097151&&(n+=String.fromCharCode(240|i>>>18&7,128|i>>>12&63,128|i>>>6&63,128|63&i));return n}function a(t){for(var i=Array(t.length>>2),e=0;e>5]|=(255&t.charCodeAt(e/8))<<24-e%32;return i}function o(t){for(var i="",e=0;e<32*t.length;e+=8)i+=String.fromCharCode(t[e>>5]>>>24-e%32&255);return i}function h(t,i){t[i>>5]|=128<<24-i%32,t[15+(i+64>>9<<4)]=i;for(var e=Array(80),n=1732584193,r=-271733879,s=-1732584194,a=271733878,o=-1009589776,h=0;h>16)+(i>>16)+(e>>16)<<16|65535&e}function d(t,i){return t<>>32-i}function _(t){this.value=t,this.listeners=[]}function c(){this.queue=[]}_.prototype.addListener=function(t){this.listeners.push(t)},_.prototype.addListenerAndFire=function(t){this.listeners.push(t),t(this.value)},_.prototype.removeListener=function(t){var i,e;i=this.listeners,(e=function(t,i){if(!t)return-1;for(var e=0;e=0&&i.splice(e,1)},_.prototype.get=function(){return this.value},_.prototype.set=function(t){this.value=t;for(var i=0;i=0&&navigator.userAgent.indexOf("Chrome")<0;function m(t){if(!t)return null;for(var i=new Uint8Array(t.length),e=0;e1e8)throw"Monster fetch!";r.setRequestHeader("Range","bytes="+e.start+"-"+e.end),e.end-e.start+1}r.onreadystatechange=function(){if(4==r.readyState)return 200==r.status||206==r.status?i(r.responseText):i(null)},e.opts.credentials&&(r.withCredentials=!0),r.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))},b.prototype.salted=function(){var t=function(t){var i={};for(var e in t)i[e]=t[e];return i}(this.opts);return t.salt=!0,new b(this.url,this.start,this.end,t)},b.prototype.getURL=function(){return this.opts.resolver?this.opts.resolver(this.url).then((function(t){return"string"==typeof t?t:t.url})):Promise.resolve(this.url)},b.prototype.fetch=function(i,e){var n=this,r=(e=e||{}).attempt||1,s=e.truncatedLength;if(r>3)return i(null);this.getURL().then((function(a){try{var o;e.timeout&&!n.opts.credentials&&(o=setTimeout((function(){return console.log("timing out "+a),l.abort(),i(null,"Timeout")}),e.timeout));var h,l=new XMLHttpRequest;if((p||n.opts.salt)&&a.indexOf("?")<0&&(a=a+"?salt="+t(Date.now()+","+ ++v)),l.open("GET",a,!0),l.overrideMimeType("text/plain; charset=x-user-defined"),n.end){if(n.end-n.start>1e8)throw"Monster fetch!";l.setRequestHeader("Range","bytes="+n.start+"-"+n.end),h=n.end-n.start+1}l.responseType="arraybuffer",l.onreadystatechange=function(){if(4==l.readyState){if(o&&clearTimeout(o),200==l.status||206==l.status){if(l.response){var t=l.response.byteLength;return!h||h==t||s&&t==s?i(l.response):n.fetch(i,{attempt:r+1,truncatedLength:t})}if(l.mozResponseArrayBuffer)return i(l.mozResponseArrayBuffer);var e=l.responseText;return!h||h==e.length||s&&e.length==s?i(m(l.responseText)):n.fetch(i,{attempt:r+1,truncatedLength:e.length})}return n.fetch(i,{attempt:r+1})}},n.opts.credentials&&(l.withCredentials=!0),l.send()}catch(t){return i(null)}})).catch((function(t){return console.log(t),i(null,t)}))};var w=new ArrayBuffer(8);new Uint8Array(w),new Float32Array(w);function y(t,i){return t[i+3]<<24|t[i+2]<<16|t[i+1]<<8|t[i]}var g=e(398);function k(t,i){this.block=t,this.offset=i}function A(t,i,e){var n=4294967296*(255&t[i+6])+16777216*(255&t[i+5])+65536*(255&t[i+4])+256*(255&t[i+3])+(255&t[i+2]),r=t[i+1]<<8|t[i];return 0!=n||0!=r||e?new k(n,r):null}function C(t,i){i=Math.min(i||1,t.byteLength-50);for(var e=[],n=[0],r=0;n[0]n._max&&(n._max=t._max):(e.push(n),n=t)})),e.push(n),this._ranges=e}function L(t,i){return t._mini._min?1:t._maxt._max?1:0}R.prototype.min=function(){return this._min},R.prototype.max=function(){return this._max},R.prototype.contains=function(t){return t>=this._min&&t<=this._max},R.prototype.isContiguous=function(){return!0},R.prototype.ranges=function(){return[this]},R.prototype._pushRanges=function(t){t.push(this)},R.prototype.toString=function(){return"["+this._min+"-"+this._max+"]"},I.prototype.min=function(){return this._ranges[0].min()},I.prototype.max=function(){return this._ranges[this._ranges.length-1].max()},I.prototype.lower_bound=function(t){var i=this.ranges();if(t>this.max())return i.length;if(ti[r]._max)e=r+1;else{if(!(tt._max&&(t._max=e[n]._max),this._ranges.splice(i,n-i+1,t)}}else this._ranges.push(t)},I.prototype.isContiguous=function(){return this._ranges.length>1},I.prototype.ranges=function(){return this._ranges},I.prototype._pushRanges=function(t){for(var i=0;i0&&(t+=","),t+=this._ranges[i].toString();return t};function T(){}function U(t,i){return new Promise((function(e,n){var r,s,a,o;r=new b(t),s=new b(i),a=function(t,i){i?n(i):e(t)},(o=new T).data=r,o.tbi=s,o.tbi.fetch((function(t){if(!t)return a(null,"Couldn't access Tabix");var i=C(t,t.byteLength),e=new Uint8Array(i);if(21578324!=y(e,0))return a(null,"Not a tabix index");var n=y(e,4);o.format=y(e,8),o.colSeq=y(e,12),o.colStart=y(e,16),o.colEnd=y(e,20),o.meta=y(e,24),o.skip=y(e,28),y(e,32),o.indices=[];var r=36;o.chrToIndex={},o.indexToChr=[];for(var s=0;s0&&(p+=65536),p0&&(o.indices[u]=new Uint8Array(i,d,r-d))}o.headerMax=f,a(o)}),{timeout:5e4})}))}function E(t){const i=t.Adapters.get("BaseLZAdapter");t.Adapters.add("TabixUrlSource",class extends i{constructor(t){if(super(t),!t.parser_func||!t.url_data&&!t.reader)throw new Error("Tabix source is missing required configuration options");if(this.parser=t.parser_func,this.url_data=t.url_data,this.url_tbi=t.url_tbi||`${this.url_data}.tbi`,this._overfetch=t.overfetch||0,this._overfetch<0||this._overfetch>1)throw new Error("Overfetch must be specified as a fraction (0-1) of the requested region size");this.url_data?this._reader_promise=U(this.url_data,this.url_tbi).catch((function(){throw new Error("Failed to create a tabix reader from the provided URL")})):this._reader_promise=Promise.resolve(t.reader)}_performRequest(t){return new Promise(((i,e)=>{const n=t.start,r=t.end,s=this._overfetch*(r-n),a=t.start-s,o=t.end+s;this._reader_promise.then((n=>{n.fetch(t.chr,a,o,(function(t,n){n&&e(new Error("Could not read requested region. This may indicate an error with the .tbi index.")),i(t)}))}))}))}_normalizeResponse(t){return t.map(this.parser)}})}T.prototype.blocksForRange=function(t,i,e){var n=this.indices[t];if(!n)return[];for(var r=function(t,i){var e,n=[];for(--i,n.push(0),e=1+(t>>26);e<=1+(i>>26);++e)n.push(e);for(e=9+(t>>23);e<=9+(i>>23);++e)n.push(e);for(e=73+(t>>20);e<=73+(i>>20);++e)n.push(e);for(e=585+(t>>17);e<=585+(i>>17);++e)n.push(e);for(e=4681+(t>>14);e<=4681+(i>>14);++e)n.push(e);return n}(i,e),s=[],a=0;a>14,v-1),w=Math.min(e>>14,v-1);for(a=m;a<=w;++a){var g=A(n,f+4+8*a);g&&((!p||g.block=p.block&&C.maxv.offset>=p.offset&&k.push(C)}h=k;var R=[];for(a=0;a0){var L=R[0];for(a=1;a=a.length)return n(l);if(h){var s=new Uint8Array(h);return r.readRecords(s,a[f].minv.offset,l,i,e,o),h=null,++f,t()}var u=a[f],d=u.minv.block,_=u.maxv.block+65536;r.data.slice(d,_-d).fetch((function(i){try{return h=C(i,u.maxv.block-u.minv.block+1),t()}catch(t){return n(null,t)}}))}()}catch(t){n(null,t)}},T.prototype.readRecords=function(t,i,e,n,r,s){t:for(;;){for(var a="";i0&&(f=parseInt(h[this.colEnd-1])),65536&this.format&&++l,l<=r&&f>=n&&e.push(a)}continue t}a+=String.fromCharCode(o)}return}},T.prototype.fetchHeader=function(t,i){var e={metaOnly:!0,skipped:!1,nLines:0};i=i||e,Object.keys(e).forEach((function(t){i.hasOwnProperty(t)||(i[t]=e[t])}));var n=this;n.data.slice(0,n.headerMax).fetch((function(e){if(!e)return t(null,"Fetch failed");for(var r=new Uint8Array(C(e,e.byteLength)),s=0,a="",o=[];s{"use strict";var t={d:(e,o)=>{for(var a in o)t.o(o,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:o[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>i});d3;Math.sqrt(3);function o(t){return JSON.parse(JSON.stringify(t))}const a=["highlight","select","fade","hide"],l=["highlighted","selected","faded","hidden"],s=["unhighlight","deselect","unfade","show"];function n(t){const e=t.Widgets.get("_Button"),n=t.Widgets.get("BaseWidget");const i=function(){const e=t.Layouts.get("tooltip","standard_association");return e.html+='Condition on Variant
                                                                                                                                                                                                                                                                                ',e}(),r=function(){const e=t.Layouts.get("toolbar","standard_association");return e.widgets.push({type:"covariates_model",button_html:"Model",button_title:"Show and edit covariates currently in model",position:"left"}),e}();t.Widgets.add("covariates_model",class extends n{initialize(){this.parent_plot.state.model=this.parent_plot.state.model||{},this.parent_plot.state.model.covariates=this.parent_plot.state.model.covariates||[],this.parent_plot.CovariatesModel={button:this,add:t=>{const e=this.parent_plot,a=o(t);"object"==typeof t&&"string"!=typeof a.html&&(a.html="function"==typeof t.toHTML?t.toHTML():t.toString());for(let t=0;t{const e=this.parent_plot;if(void 0===e.state.model.covariates[t])throw new Error(`Unable to remove model covariate, invalid index: ${t.toString()}`);return e.state.model.covariates.splice(t,1),e.applyState(),e.CovariatesModel.updateWidget(),e},removeAll:()=>{const t=this.parent_plot;return t.state.model.covariates=[],t.applyState(),t.CovariatesModel.updateWidget(),t},updateWidget:()=>{this.button.update(),this.button.menu.update()}}}update(){return this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=this.button.menu.inner_selector;if(t.html(""),void 0!==this.parent_plot.state.model.html&&t.append("div").html(this.parent_plot.state.model.html),this.parent_plot.state.model.covariates.length){t.append("h5").html(`Model Covariates (${this.parent_plot.state.model.covariates.length})`);const e=t.append("table");this.parent_plot.state.model.covariates.forEach(((t,o)=>{const a="object"==typeof t&&"string"==typeof t.html?t.html:t.toString(),l=e.append("tr");l.append("td").append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","0em").on("click",(()=>this.parent_plot.CovariatesModel.removeByIdx(o))).html("×"),l.append("td").html(a)})),t.append("button").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}`).style("margin-left","4px").html("× Remove All Covariates").on("click",(()=>this.parent_plot.CovariatesModel.removeAll()))}else t.append("i").html("no covariates in model")})),this.button.preUpdate=()=>{let t="Model";const e=this.parent_plot.state.model.covariates.length;if(e){t+=` (${e} ${e>1?"covariates":"covariate"})`}this.button.setHtml(t).disable(!1)},this.button.show()),this}}),t.Widgets.add("data_layers",class extends n{update(){return"string"!=typeof this.layout.button_html&&(this.layout.button_html="Data Layers"),"string"!=typeof this.layout.button_title&&(this.layout.button_title="Manipulate Data Layers (sort, dim, show/hide, etc.)"),this.button||(this.button=new e(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html("");const t=this.button.menu.inner_selector.append("table");return this.parent_panel._data_layer_ids_by_z_index.slice().reverse().forEach(((e,o)=>{const n=this.parent_panel.data_layers[e],i="string"!=typeof n.layout.name?n.id:n.layout.name,r=t.append("tr");r.append("td").html(i),this.layout.statuses.forEach((t=>{const e=l.indexOf(t),o=a[e];let i,d,u;n._global_statuses[t]?(i=s[e],d=`un${o}AllElements`,u="-highlighted"):(i=a[e],d=`${o}AllElements`,u=""),r.append("td").append("a").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}${u}`).style("margin-left","0em").on("click",(()=>{n[d](),this.button.menu.populate()})).html(i)}));const d=0===o,u=o===this.parent_panel._data_layer_ids_by_z_index.length-1,p=r.append("td");p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${u?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveBack(),this.button.menu.populate()})).html("▾").attr("title","Move layer down (further back)"),p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-middle lz-toolbar-button-${this.layout.color}${d?"-disabled":""}`).style("margin-left","0em").on("click",(()=>{n.moveForward(),this.button.menu.populate()})).html("▴").attr("title","Move layer up (further front)"),p.append("a").attr("class","lz-toolbar-button lz-toolbar-button-group-end lz-toolbar-button-red").style("margin-left","0em").on("click",(()=>(confirm(`Are you sure you want to remove the ${i} layer? This cannot be undone.`)&&n.parent.removeDataLayer(e),this.button.menu.populate()))).html("×").attr("title","Remove layer")})),this})),this.button.show()),this}}),t.Layouts.add("tooltip","covariates_model_association",i),t.Layouts.add("toolbar","covariates_model_plot",r)}"undefined"!=typeof LocusZoom&&LocusZoom.use(n);const i=n;LzWidgetAddons=e.default})(); //# sourceMappingURL=lz-widget-addons.min.js.map \ No newline at end of file diff --git a/dist/locuszoom.app.min.js b/dist/locuszoom.app.min.js index 3c78cc18..9f76a52c 100644 --- a/dist/locuszoom.app.min.js +++ b/dist/locuszoom.app.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.14.0-beta.4 */ -var LocusZoom;(()=>{var t={483:(t,e,s)=>{"use strict";const i=s(919);t.exports=function(t,...e){if(!t){if(1===e.length&&e[0]instanceof Error)throw e[0];throw new i(e)}}},919:(t,e,s)=>{"use strict";const i=s(820);t.exports=class extends Error{constructor(t){super(t.filter((t=>""!==t)).map((t=>"string"==typeof t?t:t instanceof Error?t.message:i(t))).join(" ")||"Unknown error"),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e.assert)}}},820:t=>{"use strict";t.exports=function(...t){try{return JSON.stringify.apply(null,t)}catch(t){return"[Cannot display object: "+t.message+"]"}}},681:(t,e,s)=>{"use strict";const i=s(483),a={};e.B=class{constructor(){this._items=[],this.nodes=[]}add(t,e){const s=[].concat((e=e||{}).before||[]),a=[].concat(e.after||[]),n=e.group||"?",o=e.sort||0;i(!s.includes(n),`Item cannot come before itself: ${n}`),i(!s.includes("?"),"Item cannot come before unassociated items"),i(!a.includes(n),`Item cannot come after itself: ${n}`),i(!a.includes("?"),"Item cannot come after unassociated items"),Array.isArray(t)||(t=[t]);for(const e of t){const t={seq:this._items.length,sort:o,before:s,after:a,group:n,node:e};this._items.push(t)}if(!e.manual){const t=this._sort();i(t,"item","?"!==n?`added into group ${n}`:"","created a dependencies error")}return this.nodes}merge(t){Array.isArray(t)||(t=[t]);for(const e of t)if(e)for(const t of e._items)this._items.push(Object.assign({},t));this._items.sort(a.mergeSort);for(let t=0;tt.sort===e.sort?0:t.sort{function e(t){if("string"==typeof t.source.flags)return t.source.flags;var e=[];return t.global&&e.push("g"),t.ignoreCase&&e.push("i"),t.multiline&&e.push("m"),t.sticky&&e.push("y"),t.unicode&&e.push("u"),e.join("")}t.exports=function t(s){if("function"==typeof s)return s;var i=Array.isArray(s)?[]:{};for(var a in s){var n=s[a],o={}.toString.call(n).slice(8,-1);i[a]="Array"==o||"Object"==o?t(n):"Date"==o?new Date(n.getTime()):"RegExp"==o?RegExp(n.source,e(n)):n}return i}}},e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={exports:{}};return t[i](a,a.exports,s),a.exports}s.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return s.d(e,{a:e}),e},s.d=(t,e)=>{for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),s.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";s.d(i,{default:()=>ds});var t={};s.r(t),s.d(t,{AssociationLZ:()=>M,BaseAdapter:()=>$,BaseApiAdapter:()=>z,BaseLZAdapter:()=>k,BaseUMAdapter:()=>E,GeneConstraintLZ:()=>A,GeneLZ:()=>N,GwasCatalogLZ:()=>S,LDServer:()=>O,PheWASLZ:()=>j,RecombLZ:()=>T,StaticSource:()=>L});var e={};s.r(e),s.d(e,{htmlescape:()=>F,is_numeric:()=>H,log10:()=>D,logtoscinotation:()=>U,neglog10:()=>B,scinotation:()=>q,urlencode:()=>G});var a={};s.r(a),s.d(a,{BaseWidget:()=>yt,_Button:()=>ft,display_options:()=>Ot,download:()=>vt,download_png:()=>wt,filter_field:()=>xt,menu:()=>St,move_panel_down:()=>kt,move_panel_up:()=>zt,region_scale:()=>bt,remove_panel:()=>$t,resize_to_data:()=>Nt,set_state:()=>Tt,shift_region:()=>Et,title:()=>mt,toggle_legend:()=>At,zoom_region:()=>Mt});var n={};s.r(n),s.d(n,{categorical_bin:()=>Vt,effect_direction:()=>Qt,if_value:()=>Zt,interpolate:()=>Xt,numerical_bin:()=>Kt,ordinal_cycle:()=>Wt,stable_choice:()=>Yt});var o={};s.r(o),s.d(o,{BaseDataLayer:()=>ie,annotation_track:()=>ne,arcs:()=>he,category_scatter:()=>me,genes:()=>de,highlight_regions:()=>re,line:()=>_e,orthogonal_line:()=>ge,scatter:()=>fe});var r={};s.r(r),s.d(r,{data_layer:()=>es,panel:()=>ss,plot:()=>is,toolbar:()=>ts,toolbar_widgets:()=>Qe,tooltip:()=>Xe});const l="0.14.0-beta.4";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}class d{constructor(t,e,s={},i=null,a=null){this.key=t,this.value=e,this.metadata=s,this.prev=i,this.next=a}}class u{constructor(t=3){if(this._max_size=t,this._cur_size=0,this._store=new Map,this._head=null,this._tail=null,null===t||t<0)throw new Error('Cache "max_size" must be >= 0')}has(t){return this._store.has(t)}get(t){const e=this._store.get(t);return e?(this._head!==e&&this.add(t,e.value),e.value):null}add(t,e,s={}){if(0===this._max_size)return;const i=this._store.get(t);i&&this._remove(i);const a=new d(t,e,s,null,this._head);if(this._head?this._head.prev=a:this._tail=a,this._head=a,this._store.set(t,a),this._max_size>=0&&this._cur_size>=this._max_size){const t=this._tail;this._tail=this._tail.prev,this._remove(t)}this._cur_size+=1}clear(){this._head=null,this._tail=null,this._cur_size=0,this._store=new Map}remove(t){const e=this._store.get(t);return!!e&&(this._remove(e),!0)}_remove(t){null!==t.prev?t.prev.next=t.next:this._head=t.next,null!==t.next?t.next.prev=t.prev:this._tail=t.prev,this._store.delete(t.key),this._cur_size-=1}find(t){let e=this._head;for(;e;){const s=e.next;if(t(e))return e;e=s}}}var _=s(957),p=s.n(_);function g(t){return"object"!=typeof t?t:p()(t)}var y=s(681);function f(t,e,s,i=!0){if(!s.length)return[];const a=s.map((t=>function(t){const e=/^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(t);if(!e)throw new Error(`Unable to parse dependency specification: ${t}`);let{name_alone:s,name_deps:i,deps:a}=e.groups;return s?[s,[]]:(a=a.split(/\s*,\s*/),[i,a])}(t))),n=new Map(a),o=new y.B;for(let[t,e]of n.entries())try{o.add(t,{after:e,group:t})}catch(e){throw new Error(`Invalid or possible circular dependency specification for: ${t}`)}const r=o.nodes,l=new Map;for(let s of r){const i=e.get(s);if(!i)throw new Error(`Data has been requested from source '${s}', but no matching source was provided`);const a=n.get(s)||[],o=Promise.all(a.map((t=>l.get(t)))).then((e=>{const a=Object.assign({_provider_name:s},t);return i.getData(a,...e)}));l.set(s,o)}return Promise.all([...l.values()]).then((t=>i?t[t.length-1]:t))}function m(t,e){const s=new Map;for(let i of t){const t=i[e];if(void 0===t)throw new Error(`All records must specify a value for the field "${e}"`);if("object"==typeof t)throw new Error("Attempted to group on a field with non-primitive values");let a=s.get(t);a||(a=[],s.set(t,a)),a.push(i)}return s}function b(t,e,s,i,a){const n=m(s,a),o=[];for(let s of e){const e=s[i],a=n.get(e)||[];a.length?o.push(...a.map((t=>Object.assign({},g(t),g(s))))):"inner"!==t&&o.push(g(s))}if("outer"===t){const t=m(e,i);for(let e of s){const s=e[a];(t.get(s)||[]).length||o.push(g(e))}}return o}function x(t,e,s,i){return b("left",...arguments)}const v=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function w(t,e=!1){const s=t&&t.match(v);if(s)return s.slice(1);if(e)return null;throw new Error(`Could not understand marker format for ${t}. Should be of format chr:pos or chr:pos_ref/alt`)}class ${constructor(){throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.')}}class z extends ${}class k extends class extends class{constructor(t={}){this._config=t;const{cache_enabled:e=!0,cache_size:s=3}=t;this._enable_cache=e,this._cache=new u(s)}_buildRequestOptions(t,e){return Object.assign({},t)}_getCacheKey(t){if(this._enable_cache)throw new Error("Method not implemented");return null}_performRequest(t){throw new Error("Not implemented")}_normalizeResponse(t,e){return t}_annotateRecords(t,e){return t}_postProcessResponse(t,e){return t}getData(t={},...e){t=this._buildRequestOptions(t,...e);const s=this._getCacheKey(t);let i;return this._enable_cache&&this._cache.has(s)?i=this._cache.get(s):(i=Promise.resolve(this._performRequest(t)).then((e=>this._normalizeResponse(e,t))),this._cache.add(s,i,t._cache_meta),i.catch((t=>this._cache.remove(s)))),i.then((t=>g(t))).then((e=>this._annotateRecords(e,t))).then((e=>this._postProcessResponse(e,t)))}}{constructor(t={}){super(t),this._url=t.url}_getCacheKey(t){return this._getURL(t)}_getURL(t){return this._url}_performRequest(t){const e=this._getURL(t);if(!this._url)throw new Error('Web based resources must specify a resource URL as option "url"');return fetch(e).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}_normalizeResponse(t,e){return"string"==typeof t?JSON.parse(t):t}}{constructor(t={}){t.params&&(console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'),Object.assign(t,t.params||{}),delete t.params),super(t);const{prefix_namespace:e=!0,limit_fields:s}=t;this._prefix_namespace=e,this._limit_fields=!!s&&new Set(s)}_getCacheKey(t){let{chr:e,start:s,end:i}=t;const a=this._cache.find((({metadata:t})=>e===t.chr&&s>=t.start&&i<=t.end));return a&&({chr:e,start:s,end:i}=a.metadata),t._cache_meta={chr:e,start:s,end:i},`${e}_${s}_${i}`}_postProcessResponse(t,e){if(!this._prefix_namespace||!Array.isArray(t))return t;const{_limit_fields:s}=this,{_provider_name:i}=e;return t.map((t=>Object.entries(t).reduce(((t,[e,a])=>(s&&!s.has(e)||(t[`${i}:${e}`]=a),t)),{})))}_findPrefixedKey(t,e){const s=new RegExp(`:${e}$`),i=Object.keys(t).find((t=>s.test(t)));if(!i)throw new Error(`Could not locate the required key name: ${e} in dependent data`);return i}}class E extends k{constructor(t={}){super(t),this._genome_build=t.genome_build||t.build}_validateBuildSource(t,e){if(t&&e||!t&&!e)throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`)}_normalizeResponse(t,e){let s=super._normalizeResponse(...arguments);if(s=s.data||s,Array.isArray(s))return s;const i=Object.keys(s),a=s[i[0]].length;if(!i.every((function(t){return s[t].length===a})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const n=[],o=Object.keys(s);for(let t=0;t25||"GRCh38"===s)return Promise.resolve([]);e=`{${e.join(" ")} }`;const i=this._getURL(t),a=JSON.stringify({query:e});return fetch(i,{method:"POST",body:a,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}_normalizeResponse(t){if("string"!=typeof t)return t;return JSON.parse(t).data}}class O extends E{constructor(t){t.limit_fields||(t.limit_fields=["variant2","position2","correlation"]),super(t)}__find_ld_refvar(t,e){const s=this._findPrefixedKey(e[0],"variant"),i=this._findPrefixedKey(e[0],"log_pvalue");let a,n={};if(t.ldrefvar)a=t.ldrefvar,n=e.find((t=>t[s]===a))||{};else{let t=0;for(let o of e){const{[s]:e,[i]:r}=o;r>t&&(t=r,a=e,n=o)}}n.lz_is_ld_refvar=!0;const o=w(a,!0);if(!o)throw new Error("Could not request LD for a missing or incomplete marker format");const[r,l,h,c]=o;a=`${r}:${l}`,h&&c&&(a+=`_${h}/${c}`);const d=+l;return d&&t.ldrefvar&&t.chr&&(r!==String(t.chr)||dt.end)?(t.ldrefvar=null,this.__find_ld_refvar(t,e)):a}_buildRequestOptions(t,e){if(!e)throw new Error("LD request must depend on association data");const s=super._buildRequestOptions(...arguments);if(!e.length)return s._skip_request=!0,s;s.ld_refvar=this.__find_ld_refvar(t,e);const i=t.genome_build||this._config.build||"GRCh37";let a=t.ld_source||this._config.source||"1000G";const n=t.ld_pop||this._config.population||"ALL";return"1000G"===a&&"GRCh38"===i&&(a="1000G-FRZ09"),this._validateBuildSource(i,null),Object.assign({},s,{genome_build:i,ld_source:a,ld_population:n})}_getURL(t){const e=this._config.method||"rsquare",{chr:s,start:i,end:a,ld_refvar:n,genome_build:o,ld_source:r,ld_population:l}=t;return[super._getURL(t),"genome_builds/",o,"/references/",r,"/populations/",l,"/variants","?correlation=",e,"&variant=",encodeURIComponent(n),"&chrom=",encodeURIComponent(s),"&start=",encodeURIComponent(i),"&stop=",encodeURIComponent(a)].join("")}_getCacheKey(t){const e=super._getCacheKey(t),{ld_refvar:s,ld_source:i,ld_population:a}=t;return`${e}_${s}_${i}_${a}`}_performRequest(t){if(t._skip_request)return Promise.resolve([]);const e=this._getURL(t);let s={data:{}},i=function(t){return fetch(t).then().then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){s.data[e]=(s.data[e]||[]).concat(t.data[e])})),t.next?i(t.next):s}))};return i(e)}}class T extends E{constructor(t){t.limit_fields||(t.limit_fields=["position","recomb_rate"]),super(t)}_getURL(t){const e=t.genome_build||this._config.build;let s=this._config.source;this._validateBuildSource(e,s);const i=e?`&build=${e}`:` and id in ${s}`;return`${super._getURL(t)}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${i}`}}class L extends k{constructor(t={}){super(...arguments);const{data:e}=t;if(!e||Array.isArray(t))throw new Error("'StaticSource' must provide data as required option 'config.data'");this._data=e}_performRequest(t){return Promise.resolve(this._data)}}class j extends E{_getURL(t){const e=(t.genome_build?[t.genome_build]:null)||this._config.build;if(!e||!Array.isArray(e)||!e.length)throw new Error(["Adapter",this.constructor.name,"requires that you specify array of one or more desired genome build names"].join(" "));return[super._getURL(t),"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",e.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}_getCacheKey(t){return this._getURL(t)}}const P=new c;for(let[e,s]of Object.entries(t))P.add(e,s);P.add("StaticJSON",L),P.add("LDLZ2",O);const R=P,I=d3,C={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function D(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function B(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function U(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function q(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function F(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function H(t){return"number"==typeof t}function G(t){return encodeURIComponent(t)}const J=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,s]of Object.entries(e))J.add(t,s);const Z=J;class K{constructor(t){if(!/^(?:\w+:\w+|^\w+)(?:\|\w+)*$/.test(t))throw new Error(`Invalid field specifier: '${t}'`);const[e,...s]=t.split("|");this.full_name=t,this.field_name=e,this.transformations=s.map((t=>Z.get(t)))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[this.field_name]?s=t[this.field_name]:e&&void 0!==e[this.field_name]&&(s=e[this.field_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const V=/^(\*|[\w]+)/,W=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function Y(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=V.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=V.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=W.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function X(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function Q(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const n=t[s.attr];1===e.length?void 0!==n&&a.push([s.attr]):a.push(...Q(n,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...Q(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...Q(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[n,o]of Object.entries(t))a.push(...Q(o,e).map((t=>[n].concat(t)))),"*"===s.attr&&a.push(...Q(o,i).map((t=>[n].concat(t))))}}else if(s.attrs)for(let[e,n]of Object.entries(t)){const[t,o,r]=X(n,s.attrs);r===s.value&&a.push(...Q(n,i).map((t=>[e].concat(t))))}const n=(o=a,r=JSON.stringify,[...new Map(o.map((t=>[r(t),t]))).values()]);var o,r;return n.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),n}function tt(t,e){const s=function(t,e){let s=[];for(let i of Q(t,e))s.push(X(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=Y(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const et=Math.sqrt(3),st={draw(t,e){const s=-Math.sqrt(e/(3*et));t.moveTo(0,2*-s),t.lineTo(-et*s,s),t.lineTo(et*s,s),t.closePath()}};function it(t,e){if(e=e||{},!t||"object"!=typeof t||"object"!=typeof e)throw new Error("Layout and shared namespaces must be provided as objects");for(let[s,i]of Object.entries(t))"namespace"===s?Object.keys(i).forEach((t=>{const s=e[t];s&&(i[t]=s)})):null!==i&&"object"==typeof i&&(t[s]=it(i,e));return t}function at(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=at(t[s],e[s])):t[s]=nt(e[s])}return t}function nt(t){return JSON.parse(JSON.stringify(t))}function ot(t){if(!t)return null;if("triangledown"===t)return st;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return I[e]||null}function rt(t,e,s=null){const i=new Set;if(!s){if(!e.length)return i;const t=e.join("|");s=new RegExp(`(?:{{)?(?:#if *)?((?:${t}):\\w+)`,"g")}for(const a of Object.values(t)){const t=typeof a;let n=[];if("string"===t){let t;for(;null!==(t=s.exec(a));)n.push(t[1])}else{if(null===a||"object"!==t)continue;n=rt(a,e,s)}for(let t of n)i.add(t)}return i}function lt(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>lt(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,n)=>(a[n]=lt(t[n],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const n=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(n,s)}}function ht(t,e,s){return function(t,e,s){return tt(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function ct(t,e){return function(t,e){return tt(t,e).map((t=>t[2]))}(t,e)}class dt{constructor(t,e,s){this._callable=ls.get(t),this._initiator=e,this._params=s||[]}getData(t,...e){const s={plot_state:t,data_layer:this._initiator};return Promise.resolve(this._callable(s,e,...this._params))}}const ut=class{constructor(t){this._sources=t}config_to_sources(t={},e=[],s){const i=new Map,a=Object.keys(t);let n=e.find((t=>"fetch"===t.type));n||(n={type:"fetch",from:a},e.unshift(n));const o=/^\w+$/;for(let[e,s]of Object.entries(t)){if(!o.test(e))throw new Error(`Invalid namespace name: '${e}'. Must contain only alphanumeric characters`);const t=this._sources.get(s);if(!t)throw new Error(`A data layer has requested an item not found in DataSources: data type '${e}' from ${s}`);i.set(e,t),n.from.find((t=>t.split("(")[0]===e))||n.from.push(e)}let r=Array.from(n.from);for(let t of e){let{type:e,name:a,requires:n,params:o}=t;if("fetch"!==e){let l=0;if(a||(a=t.name=`join${l}`,l+=1),i.has(a))throw new Error(`Configuration error: within a layer, join name '${a}' must be unique`);n.forEach((t=>{if(!i.has(t))throw new Error(`Data operation cannot operate on unknown provider '${t}'`)}));const h=new dt(e,s,o);i.set(a,h),r.push(`${a}(${n.join(", ")})`)}}return[i,r]}getData(t,e,s){return s.length?f(t,e,s,!0):Promise.resolve([])}};function _t(){return{showing:!1,selector:null,content_selector:null,hide_delay:null,show:(t,e)=>(this.curtain.showing||(this.curtain.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&>(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function pt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function gt(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class yt{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&>(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class ft{constructor(t){if(!(t instanceof yt))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),n=this.menu.outer_selector.node().getBoundingClientRect(),o=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-n.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-n.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),_=Math.min(o+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${_}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(gt,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class mt extends yt{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class bt extends yt{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(qt(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&>(this.selector,this.layout.style),this}}class xt extends yt{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class vt extends yt{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=I.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+I.select(this).attr("dy").substring(-2).slice(0,-2);I.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class wt extends vt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((n,o)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{n(URL.createObjectURL(t))}))},r.src=t}))}))}}class $t extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),I.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),I.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class zt extends yt{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class kt extends yt{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot._panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class Et extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${qt(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class Mt extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class St extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class Nt extends yt{constructor(t){super(...arguments)}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class At extends yt{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new ft(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class Ot extends yt{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,n={};s.forEach((t=>{const e=a[t];void 0!==e&&(n[t]=nt(e))})),this._selected_item="default",this.button=new ft(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,o=(a,o,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==o[t];i.layout[t]=e?o[t]:n[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return o(r,n,"default"),a.options.forEach(((t,e)=>o(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class Tt extends yt{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new ft(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,n)=>{const o=s.append("tr"),r=`${e}${n}`;o.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",n).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),o.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const Lt=new c;for(let[t,e]of Object.entries(a))Lt.add(t,e);const jt=Lt;class Pt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{this.addWidget(t)})),"panel"===this.type&&I.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}addWidget(t){try{const e=jt.create(t.type,t,this);return this.widgets.push(e),e}catch(t){console.warn("Failed to create widget"),console.error(t)}}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot._panel_boundaries.dragging||this.parent_plot._interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=I.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=I.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const Rt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:14,hidden:!1};class It{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=at(this.parent.layout.legend||{},Rt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((a=>{const n=this.parent.data_layers[a].layout.legend;Array.isArray(n)&&n.forEach((a=>{const n=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),o=+a.label_size||+this.layout.label_size;let r=0,l=o/2+t/2;i=Math.max(i,o+t);const h=a.shape||"",c=ot(h);if("line"===h){const e=+a.length||16,s=o/4+t/2;n.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(gt,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;n.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(gt,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if("ribbon"===h){const e=+a.width||25,s=+a.height||e,i="horizontal"===(a.orientation||"vertical");let o=a.color_stops;const h=n.append("g"),c=h.append("g"),d=h.append("g");let u=0;if(a.tick_labels){let t;t=i?[0,e*o.length-1]:[s*o.length-1,0];const n=I.scaleLinear().domain(I.extent(a.tick_labels)).range(t),r=(i?I.axisTop:I.axisRight)(n).tickSize(3).tickValues(a.tick_labels).tickFormat((t=>t));d.call(r).attr("class","lz-axis"),u=d.node().getBoundingClientRect().height}i?(d.attr("transform",`translate(0, ${u})`),c.attr("transform",`translate(0, ${u})`)):(h.attr("transform","translate(5, 0)"),d.attr("transform",`translate(${e}, 0)`)),i||(o=o.slice(),o.reverse());for(let t=0;tt&&a>this.parent.parent.layout.width&&(s+=i,e=t,n.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(n)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const Ct={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class Dt{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"!=typeof t.id||!t.id)throw new Error('Panel layouts must specify "id"');if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`);this.id=t.id,this._initialized=!1,this._layout_idx=null,this.svg={},this.layout=at(t||{},Ct),this.parent?(this.state=this.parent.state,this._state_id=this.id,this.state[this._state_id]=this.state[this._state_id]||{}):(this.state=null,this._state_id=null),this.data_layers={},this._data_layer_ids_by_z_index=[],this._data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this._zoom_timeout=null,this._event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this._event_hooks[t]&&this._event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=at(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(gt,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id '${t.id}'; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=xe.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this._data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this._data_layer_ids_by_z_index.length+e.layout.z_index,0)),this._data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this._data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id]._layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){const e=this.data_layers[t];if(!e)throw new Error(`Unable to remove data layer, ID not found: ${t}`);return e.destroyAllTooltips(),e.svg.container&&e.svg.container.remove(),this.layout.data_layers.splice(e._layout_idx,1),delete this.state[e._state_id],delete this.data_layers[t],this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id]._layout_idx=e})),this}clearSelections(){return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height);const{cliparea:t}=this.layout,{margin:e}=this.layout;this.inner_border.attr("x",e.left).attr("y",e.top).attr("width",this.parent_plot.layout.width-(e.left+e.right)).attr("height",this.layout.height-(e.top+e.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const s=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),n=Math.pow(10,e);return t===1/0&&(t=n),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,n),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},i={},a=this.layout.axes;if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};a.x.range&&(t.start=a.x.range.start||t.start,t.end=a.x.range.end||t.end),i.x=[t.start,t.end],i.x_shifted=[t.start,t.end]}if(this.y1_extent){const e={start:t.height,end:0};a.y1.range&&(e.start=a.y1.range.start||e.start,e.end=a.y1.range.end||e.end),i.y1=[e.start,e.end],i.y1_shifted=[e.start,e.end]}if(this.y2_extent){const e={start:t.height,end:0};a.y2.range&&(e.start=a.y2.range.start||e.start,e.end=a.y2.range.end||e.end),i.y2=[e.start,e.end],i.y2_shifted=[e.start,e.end]}let{_interaction:n}=this.parent;const o=n.dragging;if(n.panel_id&&(n.panel_id===this.id||n.linked_panel_ids.includes(this.id))){let a,r=null;if(n.zooming&&"function"==typeof this.x_scale){const s=Math.abs(this.x_extent[1]-this.x_extent[0]),o=Math.round(this.x_scale.invert(i.x_shifted[1]))-Math.round(this.x_scale.invert(i.x_shifted[0]));let r=n.zooming.scale;const l=Math.floor(o*(1/r));r<1&&!isNaN(this.parent.layout.max_region_scale)?r=1/(Math.min(l,this.parent.layout.max_region_scale)/o):r>1&&!isNaN(this.parent.layout.min_region_scale)&&(r=1/(Math.max(l,this.parent.layout.min_region_scale)/o));const h=Math.floor(s*r);a=n.zooming.center-e.left-this.layout.origin.x;const c=a/t.width,d=Math.max(Math.floor(this.x_scale.invert(i.x_shifted[0])-(h-o)*c),1);i.x_shifted=[this.x_scale(d),this.x_scale(d+h)]}else if(o)switch(o.method){case"background":i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x;break;case"x_tick":I.event&&I.event.shiftKey?(i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x):(a=o.start_x-e.left-this.layout.origin.x,r=s(a/(a+o.dragged_x),3),i.x_shifted[0]=0,i.x_shifted[1]=Math.max(t.width*(1/r),1));break;case"y1_tick":case"y2_tick":{const n=`y${o.method[1]}_shifted`;I.event&&I.event.shiftKey?(i[n][0]=t.height+o.dragged_y,i[n][1]=+o.dragged_y):(a=t.height-(o.start_y-e.top-this.layout.origin.y),r=s(a/(a-o.dragged_y),3),i[n][0]=t.height,i[n][1]=t.height-t.height*(1/r))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(i[t][0]),this[`${t}_scale`].invert(i[t][1])],this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!I.event.shiftKey&&!I.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(I.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=I.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,I.event.wheelDelta||-I.event.detail||-I.event.deltaY));0!==e&&(this.parent._interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),n=this.parent._interaction,n.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this._zoom_timeout&&clearTimeout(this._zoom_timeout),this._zoom_timeout=setTimeout((()=>{this.parent._interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this._initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{const e=this.layout.axes[t];Object.keys(e).length&&!1!==e.render?(e.render=!0,e.label=e.label||null):e.render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){const s=this.layout;return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),s.height=Math.max(Math.round(+e),s.min_height)),s.cliparea.width=Math.max(this.parent_plot.layout.width-(s.margin.left+s.margin.right),0),s.cliparea.height=Math.max(s.height-(s.margin.top+s.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",s.height),this._initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this._initialized&&this.render(),this}setMargin(t,e,s,i){let a;const{cliparea:n,margin:o}=this.layout;return!isNaN(t)&&t>=0&&(o.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(o.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(o.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(o.left=Math.max(Math.round(+i),0)),o.top+o.bottom>this.layout.height&&(a=Math.floor((o.top+o.bottom-this.layout.height)/2),o.top-=a,o.bottom-=a),o.left+o.right>this.parent_plot.layout.width&&(a=Math.floor((o.left+o.right-this.parent_plot.layout.width)/2),o.left-=a,o.right-=a),["top","right","bottom","left"].forEach((t=>{o[t]=Math.max(o[t],0)})),n.width=Math.max(this.parent_plot.layout.width-(o.left+o.right),0),n.height=Math.max(this.layout.height-(o.top+o.bottom),0),n.origin.x=o.left,n.origin.y=o.top,this._initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=_t.call(this),this.loader=pt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new Pt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new It(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this._data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(I.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent._panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){const{parent:t}=this,e=this.layout.y_index;return t._panel_ids_by_y_index[e-1]&&(t._panel_ids_by_y_index[e]=t._panel_ids_by_y_index[e-1],t._panel_ids_by_y_index[e-1]=this.id,t.applyPanelYIndexesToPanelLayouts(),t.positionPanels()),this}moveDown(){const{_panel_ids_by_y_index:t}=this.parent;return t[this.layout.y_index+1]&&(t[this.layout.y_index]=t[this.layout.y_index+1],t[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this._data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this._data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this._data_promises).then((()=>{this._initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=I.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=I.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this._data_layer_ids_by_z_index.reduce(((e,a)=>{const n=s.data_layers[a];return e.concat(n.getTicks(t,i))}),[]).map((t=>{let s={};return s=at(s,e),at(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,n=1.5,o=.5+1.5*n,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(_=parseFloat(_.toFixed(c)));u.push(_),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eqt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=I.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=I.select(this).select("text");s[`${t}_ticks`][i].style&>(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const n=this.layout.axes[t].label||null;return null!==n&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(Ht(n,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof I.select(this).node().focus&&I.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";I.event&&I.event.shiftKey&&(i="move"),I.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){I.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this._data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this._data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;Dt.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Dt.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Bt={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Ut{constructor(t,e,s){this._initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this._panel_ids_by_y_index=[],this._remap_promises=[],this.layout=s,at(this.layout,Bt),this._base_layout=nt(this.layout),this.state=this.layout.state,this.lzd=new ut(e),this._external_listeners=new Map,this._event_hooks={},this._interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this._event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this._event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Dt(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this._panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this._panel_ids_by_y_index.length+e.layout.y_index,0)),this._panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this._panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id]._layout_idx=s,this._initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t]._data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i._layer_state,delete this.layout.state[i._state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){const e=this.panels[t];if(!e)throw new Error(`Unable to remove panel, ID not found: ${t}`);return this._panel_boundaries.hide(),this.clearPanelData(t),e.loader.hide(),e.toolbar.destroy(!0),e.curtain.hide(),e.svg.container&&e.svg.container.remove(),this.layout.panels.splice(e._layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id]._layout_idx=e})),this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this._initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e){const{from_layer:s,namespace:i,data_operations:a,onerror:n}=t,o=n||function(t){console.error("An error occurred while acting on an external callback",t)};if(s){const t=`${this.getBaseId()}.`,i=s.startsWith(t)?s:`${t}${s}`;let a=!1;for(let t of Object.values(this.panels))if(a=Object.values(t.data_layers).some((t=>t.getBaseId()===i)),a)break;if(!a)throw new Error(`Could not subscribe to unknown data layer ${i}`);const n=t=>{if(t.data.layer===i)try{e(t.data.content,this)}catch(t){o(t)}};return this.on("data_from_layer",n),n}if(!i)throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option");const[r,l]=this.lzd.config_to_sources(i,a),h=()=>{try{this.lzd.getData(this.state,r,l).then((t=>e(t,this))).catch(o)}catch(t){o(t)}};return this.on("data_rendered",h),h}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this._remap_promises=[],this.loading_data=!0;for(let t in this.panels)this._remap_promises.push(this.panels[t].reMap());return Promise.all(this._remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e._data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this._initialized=!1,this.svg=null,this.panels=null}mutateLayout(){Object.values(this.panels).forEach((t=>{Object.values(t.data_layers).forEach((t=>t.mutateLayout()))}))}_canInteract(t){t=t||null;const{_interaction:e}=this;return t?(void 0===e.panel_id||e.panel_id===t)&&!this.loading_data:!(e.dragging||e.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==I.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this._panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");return this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,n=e.layout.height*s;e.setDimensions(a,n),e.setOrigin(0,i),i+=n,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this._initialized&&(this._panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e of Object.values(this.panels))e.layout.interaction.x_linked&&(t.left=Math.max(t.left,e.layout.margin.left),t.right=Math.max(t.right,e.layout.margin.right));let e=0;return this._panel_ids_by_y_index.forEach((s=>{const i=this.panels[s],a=i.layout;if(i.setOrigin(0,e),e+=this.panels[s].layout.height,a.interaction.x_linked){const e=Math.max(t.left-a.margin.left,0)+Math.max(t.right-a.margin.right,0);a.width+=e,a.margin.left=t.left,a.margin.right=t.right,a.cliparea.origin.x=t.left}})),this.setDimensions(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize){I.select(this.container).classed("lz-container-responsive",!0);const t=()=>window.requestAnimationFrame((()=>this.rescaleSVG()));if(window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t),"undefined"!=typeof IntersectionObserver){const t={root:document.documentElement,threshold:.9};new IntersectionObserver(((t,e)=>{t.some((t=>t.intersectionRatio>0))&&this.rescaleSVG()}),t).observe(this.container)}const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}if(this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this._mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=_t.call(this),this.loader=pt.call(this),this._panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent._panel_ids_by_y_index.forEach(((t,e)=>{const s=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=I.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent._panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+I.event.dy);const i=t.layout.height-s;this.parent._panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent._panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent._panel_boundaries.selectors.push(s)}));const t=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=I.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+I.event.dx,this.parent._total_height+I.event.dy)})),t.call(e),this.parent._panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent._panel_ids_by_y_index[s]],a=i._getPageOrigin(),n=t.x,o=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${o}px`).style("left",`${n}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&I.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this._panel_boundaries.hide_timeout),this._panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this._panel_boundaries.hide_timeout=setTimeout((()=>{this._panel_boundaries.hide()}),300)})),this.toolbar=new Pt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this._mouse_guide.vertical.attr("x",-1),this._mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=I.mouse(this.svg.node());this._mouse_guide.vertical.attr("x",t[0]),this._mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{const{_interaction:t}=this;if(t.dragging){const e=I.mouse(this.svg.node());I.event&&I.event.preventDefault(),t.dragging.dragged_x=e[0]-t.dragging.start_x,t.dragging.dragged_y=e[1]-t.dragging.start_y,this.panels[t.panel_id].render(),t.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=I.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this._initialized=!0;const a=this.svg.node().getBoundingClientRect(),n=a.width?a.width:this.layout.width,o=a.height?a.height:this._total_height;return this.setDimensions(n,o),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof Dt&&s&&this._canInteract()))return this.stopDrag();const i=I.mouse(this.svg.node());return this._interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){const{_interaction:t}=this;if(!t.dragging)return this;if("object"!=typeof this.panels[t.panel_id])return this._interaction={},this;const e=this.panels[t.panel_id],s=(t,s,i)=>{e._data_layer_ids_by_z_index.forEach((a=>{const n=e.data_layers[a].layout[`${t}_axis`];n.axis===s&&(n.floor=i[0],n.ceiling=i[1],delete n.lower_buffer,delete n.upper_buffer,delete n.min_extent,delete n.ticks)}))};switch(t.dragging.method){case"background":case"x_tick":0!==t.dragging.dragged_x&&(s("x",1,e.x_extent),this.applyState({start:e.x_extent[0],end:e.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==t.dragging.dragged_y){const i=parseInt(t.dragging.method[1]);s("y",i,e[`y${i}_extent`])}}return this._interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function qt(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),n=Math.min(Math.max(e,0),2),o=Math.min(Math.max(a,n),12);let r=`${(t/Math.pow(10,e)).toFixed(o)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Ft(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function Ht(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const n=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(n())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},o=[];for(;i.length>0;)o.push(n());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new K(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return o.map(l).join("")}const Gt=new h;Gt.add("=",((t,e)=>t===e)),Gt.add("!=",((t,e)=>t!=e)),Gt.add("<",((t,e)=>tt<=e)),Gt.add(">",((t,e)=>t>e)),Gt.add(">=",((t,e)=>t>=e)),Gt.add("%",((t,e)=>t%e)),Gt.add("in",((t,e)=>e&&e.includes(t))),Gt.add("match",((t,e)=>t&&t.includes(e)));const Jt=Gt,Zt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Kt=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Wt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Yt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let n=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var n=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(n=i)})),null===n)return a;const t=(+e-s[n-1])/(s[n]-s[n-1]);return isFinite(t)?I.interpolate(i[n-1],i[n])(t):a}};function Qt(t,e){if(void 0===e)return null;const{beta_field:s,stderr_beta_field:i,"+":a=null,"-":n=null}=t;if(!s||!i)throw new Error("effect_direction must specify how to find required 'beta' and 'stderr_beta' fields");const o=e[s],r=e[i];if(void 0!==o)if(void 0!==r){if(o-1.96*r>0)return a;if(o+1.96*r<0)return n||null}else{if(o>0)return a;if(o<0)return n}return null}const te=new h;for(let[t,e]of Object.entries(n))te.add(t,e);te.add("if",Zt);const ee=te,se={id:"",type:"",tag:"custom_data_type",namespace:{},data_operations:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class ie{constructor(t,e){this._initialized=!1,this._layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=at(t||{},se),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=nt(this.layout),this.state={},this._state_id=null,this._layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this._tooltips={}),this._global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1},this._data_contract=new Set,this._entities=new Map,this._dependencies=[],this.mutateLayout()}render(){throw new Error("Method must be implemented")}moveForward(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e+1]&&(t[e]=t[e+1],t[e+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e-1]&&(t[e]=t[e-1],t[e-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this._layer_state.extra_fields[i]||(this._layer_state.extra_fields[i]={}),this._layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}mutateLayout(){if(this.parent_plot){const{namespace:t,data_operations:e}=this.layout;this._data_contract=rt(this.layout,Object.keys(t));const[s,i]=this.parent_plot.lzd.config_to_sources(t,e,this);this._entities=s,this._dependencies=i}}_getDataExtent(t,e){return t=t||this.data,I.extent(t,(t=>+new K(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field;let i=t[s];if(void 0===i&&/{{[^{}]*}}/.test(s)&&(i=Ht(s,t,{})),null==i)throw new Error("Unable to generate element ID");const a=i.toString().replace(/\W/g,""),n=`${this.getBaseId()}-${a}`.replace(/([:.[\],])/g,"_");return t[e]=n,n}getElementStatusNodeId(t){return null}getElementById(t){const e=I.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Jt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new K(t):null;if(this.data.length&&this._data_contract.size){const t=new Set(this._data_contract);for(let e of this.data)if(Object.keys(e).forEach((e=>t.delete(e))),!t.size)break;t.size&&console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...t]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`)}return this.data.forEach(((a,n)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(p+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=_<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-_,0),e=Math.max(c.width/2+_-u,0);y=h.x+_-c.width/2-e+t,b=h.x+_-y-7,"top"===w?(g=h.y+p-(v+c.height+8),f="down",m=c.height-1):(g=h.y+p+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+_+x+8,f="left",b=-8):(y=h.x+_-c.width-x-8,f="right",b=c.width-1),p-c.height/2<=0?(g=h.y+p-10.5-6,m=6):p+c.height/2>=d?(g=h.y+p+7+6-c.height,m=c.height-14-6):(g=h.y+p-c.height/2,m=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${f}`).style("left",`${b}px`).style("top",`${m}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:n}=t,o=Jt.get(i),r=this.getElementAnnotation(e);o(s?new K(s).resolve(e,r):e,n)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this._layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;C.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this._state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this._state_id]=t),this._layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this._tooltips[e])return this._tooltips[e]={data:t,arrow:null,selector:I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this._layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this._tooltips[e].selector.html(""),this._tooltips[e].arrow=null,this.layout.tooltip.html&&this._tooltips[e].selector.html(Ht(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this._tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this._tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this._tooltips[s]&&("object"==typeof this._tooltips[s].selector&&this._tooltips[s].selector.remove(),delete this._tooltips[s]),!e){this._layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this._tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this._tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this._tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this._tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){const s=this.layout.tooltip;if("object"!=typeof s)return this;const i=this.getElementId(t),a=(t,e,s)=>{let i=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",i=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let n;for(let o in e)n=a(t,e[o],o),null===i?i=n:"and"===s?i=i&&n:"or"===s&&(i=i||n)}}return i};let n={};"string"==typeof s.show?n={and:[s.show]}:"object"==typeof s.show&&(n=s.show);let o={};"string"==typeof s.hide?o={and:[s.hide]}:"object"==typeof s.hide&&(o=s.hide);const r=this._layer_state;var l={};C.adjectives.forEach((t=>{const e=`un${t}`;l[t]=r.status_flags[t].has(i),l[e]=!l[t]}));const h=a(l,n),c=a(l,o),d=r.status_flags.has_tooltip.has(i);return!h||!e&&!d||c?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),I.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const n=this.getElementStatusNodeId(e);null!==n&&I.select(`#${n}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const o=!this._layer_state.status_flags[t].has(a);s&&o&&this._layer_state.status_flags[t].add(a),s||o||this._layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,o),o&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!o&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!o&&s||this.parent.emit("match_requested",{value:new K(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this._layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this._layer_state.status_flags[t]=new Set}return this._global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||I.select(I.event.target).datum(),s===!!I.event.ctrlKey&&i===!!I.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a._layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=Ht(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this._layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e._state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this._entities,this._dependencies).then((t=>{this.data=t,this.applyDataMethods(),this._initialized=!0,this.parent.emit("data_from_layer",{layer:this.getBaseId(),content:nt(t)},!0)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;ie.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},ie.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},ie.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},ie.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const ae={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class ne extends ie{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");at(t,ae),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],n=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+n)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const oe={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class re extends ie{constructor(t){if(at(t,oe),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions.length&&t.namespace&&Object.keys(t.namespace).length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>I.ascending(t[s],e[s])||I.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,n){const o=a[a.length-1]||t;if(t[s]===o[s]&&t[i]<=o[e]){const s=Math.min(o[i],t[i]),n=Math.max(o[e],t[e]);t=Object.assign({},o,t,{[i]:s,[e]:n}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const le={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class he extends ie{constructor(t){t=at(t,le),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function n(t){const a=t[e.x_axis.field1],n=t[e.x_axis.field2],o=(a+n)/2,r=[[s(a),i(0)],[s(o),i(t[e.y_axis.field])],[s(n),i(0)]];return I.line().x((t=>t[0])).y((t=>t[1])).curve(I.curveNatural)(r)}const o=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(gt,e.style),o.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(o).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>n(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>n(t))),r.exit().remove(),o.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],n=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:n(t.data[s.y_axis.field]),y_max:n(0)}}}const ce={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:15,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class de extends ie{constructor(t){t=at(t,ce),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=I.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const n=I.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,n.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(n).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),n.exit().remove();const o=I.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));o.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(o).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),o.exit().remove();const r=I.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=I.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=I.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const ue={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5,tooltip:null};class _e extends ie{constructor(t){if((t=at(t,ue)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const n=t.x_scale,o=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?I.area().x((t=>+n(t[e]))).y0(+o(0)).y1((t=>+o(t[s]))):I.line().x((t=>+n(t[e]))).y((t=>+o(t[s]))).curve(I[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(gt,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;void 0===e&&(e=!0),this._global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this._global_statuses).forEach((t=>{this._global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const pe={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class ge extends ie{constructor(t){t=at(t,pe),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments)}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],n=I.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const n=+t[e](s.y);return isNaN(n)?a[i]:n}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",n).call(gt,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=I.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ye={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class fe extends ie{constructor(t){(t=at(t,ye)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),n=Math.sqrt(a/Math.PI);return{x_min:e-n,x_max:e+n,y_min:i-n,y_max:i+n}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,n=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,o=(t,a)=>{const n=+t.attr("x"),o=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",n-o),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",n+o),i&&a.attr("x2",r+l))};t._label_texts.each((function(e,a){const r=I.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>n){const e=i?I.select(t._label_lines.nodes()[a]):null;o(r,e)}})),t._label_texts.each((function(e,n){const r=I.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?I.select(t._label_lines.nodes()[n]):null;t._label_texts.each((function(){const t=I.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(o(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=o.attr("y"),c=.5*(r.topp?(g=d-+n,d=+n,u-=g):u+l.height/2>p&&(g=u-+h,u=+h,d-=g),a.attr("y",d),o.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t._label_texts.nodes();t._label_lines.attr("y2",((t,s)=>I.select(e[s]).attr("y")))}this._label_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let n=this._applyFilters();if(n.forEach((t=>{let n=e(t[this.layout.x_axis.field]),o=s(t[this.layout.y_axis.field]);isNaN(n)&&(n=-1e3),isNaN(o)&&(o=-1e3),t[i]=n,t[a]=o})),this.layout.coalesce.active&&n.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:o,x_gap:r,y_gap:l}=this.layout.coalesce;n=function(t,e,s,i,a,n,o){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function _(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function p(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],f=g>=e&&g<=s&&y>=a&&y<=n;t.lz_is_match||!f?(_(),r.push(t)):null===c?p(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=o?u.push(t):(_(),p(g,y,t))})),_(),r}(n,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(o)?s(+o):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=n.filter(t)}else e=n;this._label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const o=`lz-data_layer-${this.layout.type}-label`,r=this._label_groups.enter().append("g").attr("class",o);this._label_texts&&this._label_texts.remove(),this._label_texts=this._label_groups.merge(r).append("text").text((e=>Ht(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(gt,t.layout.label.style||{}),t.layout.label.lines&&(this._label_lines&&this._label_lines.remove(),this._label_lines=this._label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(gt,t.layout.label.lines.style||{})),this._label_groups.exit().remove()}else this._label_texts&&this._label_texts.remove(),this._label_lines&&this._label_lines.remove(),this._label_groups&&this._label_groups.remove();const o=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(n,(t=>t[this.layout.id_field])),r=I.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>ot(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;o.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(o).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),o.exit().remove(),this.layout.label&&(this.flip_labels(),this._label_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=I.select(I.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class me extends fe{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],n="string"==typeof i?i.toLowerCase():i,o="string"==typeof a?a.toLowerCase():a;return n===o?0:n{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],n=i[e],o=s[a]||[n,n];s[a]=[Math.min(o[0],n),Math.max(o[1],n)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const o=i[t];let r;switch(s){case"left":r=o[0];break;case"center":const t=o[1]-o[0];r=o[0]+(0!==t?t:o[0])/2;break;case"right":r=o[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const be=new c;for(let[t,e]of Object.entries(o))be.add(t,e);const xe=be,ve=7.301,we={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{assoc:variant|htmlescape}}
                                                                                                                                                                                                                                                                                \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
                                                                                                                                                                                                                                                                                \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
                                                                                                                                                                                                                                                                                \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
                                                                                                                                                                                                                                                                                '},$e=function(){const t=nt(we);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),ze={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

                                                                                                                                                                                                                                                                                {{gene_name|htmlescape}}

                                                                                                                                                                                                                                                                                Gene ID: {{gene_id|htmlescape}}
                                                                                                                                                                                                                                                                                Transcript ID: {{transcript_id|htmlescape}}
                                                                                                                                                                                                                                                                                {{#if pLI}}
                                                                                                                                                                                                                                                                                ConstraintExpected variantsObserved variantsConst. Metric
                                                                                                                                                                                                                                                                                Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
                                                                                                                                                                                                                                                                                o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
                                                                                                                                                                                                                                                                                Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
                                                                                                                                                                                                                                                                                o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
                                                                                                                                                                                                                                                                                pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
                                                                                                                                                                                                                                                                                o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

                                                                                                                                                                                                                                                                                {{/if}}More data on gnomAD'},ke={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{catalog:variant|htmlescape}}
                                                                                                                                                                                                                                                                                Catalog entries: {{n_catalog_matches|htmlescape}}
                                                                                                                                                                                                                                                                                Top Trait: {{catalog:trait|htmlescape}}
                                                                                                                                                                                                                                                                                Top P Value: {{catalog:log_pvalue|logtoscinotation}}
                                                                                                                                                                                                                                                                                More: GWAS catalog / dbSNP'},Ee={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
                                                                                                                                                                                                                                                                                {{access:start1|htmlescape}}-{{access:end1|htmlescape}}
                                                                                                                                                                                                                                                                                Promoter
                                                                                                                                                                                                                                                                                {{access:start2|htmlescape}}-{{access:end2|htmlescape}}
                                                                                                                                                                                                                                                                                {{#if access:target}}Target: {{access:target|htmlescape}}
                                                                                                                                                                                                                                                                                {{/if}}Score: {{access:score|htmlescape}}"},Me={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:ve},Se={id:"recombrate",namespace:{recomb:"recomb"},data_operations:[{type:"fetch",from:["recomb"]}],type:"line",tag:"recombination",z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"recomb:position"},y_axis:{axis:2,field:"recomb:recomb_rate",floor:0,ceiling:100}},Ne={namespace:{assoc:"assoc",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)"]},{type:"left_match",name:"assoc_plus_ld",requires:["assoc","ld"],params:["assoc:position","ld:position2"]}],id:"associationpvalues",type:"scatter",tag:"association",id_field:"assoc:variant",coalesce:{active:!0},point_shape:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"diamond"}},{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"assoc:beta",stderr_beta_field:"assoc:se"}},"circle"],point_size:{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:80,else:40}},color:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"#9632b8"}},{scale_function:"numerical_bin",field:"ld:correlation",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{label:"LD (r²)",label_size:14},{shape:"ribbon",orientation:"vertical",width:10,height:15,color_stops:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"],tick_labels:[0,.2,.4,.6,.8,1]}],label:null,z_index:2,x_axis:{field:"assoc:position"},y_axis:{axis:1,field:"assoc:log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(we)},Ae={id:"coaccessibility",type:"arcs",tag:"coaccessibility",namespace:{access:"access"},data_operations:[{type:"fetch",from:["access"]}],match:{send:"access:target",receive:"access:target"},id_field:"{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}",filters:[{field:"access:score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"access:start1",field2:"access:start2"},y_axis:{axis:1,field:"access:score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(Ee)},Oe=function(){let t=nt(Ne);return t=at({id:"associationpvaluescatalog",fill_opacity:.7},t),t.data_operations.push({type:"assoc_to_gwas_catalog",name:"assoc_catalog",requires:["assoc_plus_ld","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}),t.tooltip.html+='{{#if catalog:rsid}}
                                                                                                                                                                                                                                                                                See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t}(),Te={id:"phewaspvalues",type:"category_scatter",tag:"phewas",namespace:{phewas:"phewas"},data_operations:[{type:"fetch",from:["phewas"]}],point_shape:[{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"phewas:beta",stderr_beta_field:"phewas:se"}},"circle"],point_size:70,tooltip_positioning:"vertical",id_field:"{{phewas:trait_group}}_{{phewas:trait_label}}",x_axis:{field:"lz_auto_x",category_field:"phewas:trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"phewas:log_pvalue",floor:0,upper_buffer:.15},color:[{field:"phewas:trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Trait: {{phewas:trait_label|htmlescape}}
                                                                                                                                                                                                                                                                                \nTrait Category: {{phewas:trait_group|htmlescape}}
                                                                                                                                                                                                                                                                                \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
                                                                                                                                                                                                                                                                                β: {{phewas:beta|scinotation|htmlescape}}
                                                                                                                                                                                                                                                                                {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}"},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{phewas:trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"phewas:log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},Le={namespace:{gene:"gene",constraint:"constraint"},data_operations:[{type:"fetch",from:["gene","constraint(gene)"]},{name:"gene_constraint",type:"genes_to_gnomad_constraint",requires:["gene","constraint"]}],id:"genes",type:"genes",tag:"genes",id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ze)},je=at({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},nt(Le)),Pe={namespace:{assoc:"assoc",catalog:"catalog"},data_operations:[{type:"fetch",from:["assoc","catalog"]},{type:"assoc_to_gwas_catalog",name:"assoc_plus_ld",requires:["assoc","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}],id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:"#0000CC",filters:[{field:"catalog:rsid",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ke),tooltip_positioning:"top"},Re={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},Ie={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},Ce={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},De={widgets:[{type:"title",title:"LocusZoom",subtitle:`v${l}`,position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Be=function(){const t=nt(De);return t.widgets.push(nt(Re)),t}(),Ue=function(){const t=nt(De);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),qe={id:"association",tag:"association",min_height:200,height:300,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:50},y2:{label:"Recombination Rate (cM/Mb)",label_offset:46}},legend:{orientation:"vertical",origin:{x:75,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Me),nt(Se),nt(Ne)]},Fe={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:40,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Ae)]},He=function(){let t=nt(qe);return t=at({id:"associationcatalog"},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{catalog:trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"catalog:trait",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve},{field:"ld:correlation",operator:">",value:.4}],style:{"font-size":"12px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[nt(Me),nt(Se),nt(Oe)],t}(),Ge={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:55,bottom:20,left:70},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},nt(Ie)),t}(),data_layers:[nt(je)]},Je={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:55,bottom:120,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:50}},data_layers:[nt(Me),nt(Te)]},Ze={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:55,bottom:10,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Pe)]},Ke={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[nt(qe),nt(Ge)]},Ve={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[Ze,He,Ge]},We={width:800,responsive_resize:!0,toolbar:De,panels:[nt(Je),at({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"}}},nt(Ge))],mouse_guide:!1},Ye={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:nt(De),panels:[nt(Fe),function(){const t=Object.assign({height:270},nt(Ge)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},Xe={standard_association:we,standard_association_with_label:$e,standard_genes:ze,catalog_variant:ke,coaccessibility:Ee},Qe={ldlz2_pop_selector:Re,gene_selector_menu:Ie},ts={standard_panel:Ce,standard_plot:De,standard_association:Be,region_nav_plot:Ue},es={significance:Me,recomb_rate:Se,association_pvalues:Ne,coaccessibility:Ae,association_pvalues_catalog:Oe,phewas_pvalues:Te,genes:Le,genes_filtered:je,annotation_catalog:Pe},ss={association:qe,coaccessibility:Fe,association_catalog:He,genes:Ge,phewas:Je,annotation_catalog:Ze},is={standard_association:Ke,association_catalog:Ve,standard_phewas:We,coaccessibility:Ye};const as=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);const a=s.namespace;i.namespace||delete s.namespace;let n=at(s,i);return a&&(n=it(n,a)),nt(n)}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=nt(s);return"data_layer"===t&&a.namespace&&(a._auto_fields=[...rt(a,Object.keys(a.namespace))].sort()),super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return at(t,e)}renameField(){return lt(...arguments)}mutate_attrs(){return ht(...arguments)}query_attrs(){return ct(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))as.add(t,s,i);const ns=as,os=new h;function rs(t){return(e,s,...i)=>{if(2!==s.length)throw new Error("Join functions must receive exactly two recordsets");return t(...s,...i)}}os.add("left_match",rs(x)),os.add("inner_match",rs((function(t,e,s,i){return b("inner",...arguments)}))),os.add("full_outer_match",rs((function(t,e,s,i){return b("outer",...arguments)}))),os.add("assoc_to_gwas_catalog",rs((function(t,e,s,i,a){if(!t.length)return t;const n=m(e,i),o=[];for(let t of n.values()){let e,s=0;for(let i of t){const t=i[a];t>=s&&(e=i,s=t)}e.n_catalog_matches=t.length,o.push(e)}return x(t,o,s,i)}))),os.add("genes_to_gnomad_constraint",rs((function(t,e){return t.forEach((function(t){const s=`_${t.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=e[s]&&e[s].gnomad_constraint;i&&Object.keys(i).forEach((function(e){let s=i[e];void 0===t[e]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),t[e]=s)}))})),t})));const ls=os;const hs={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return I.select(t).html(""),I.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!I.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Ut(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Ft(s[2]),e=Ft(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Ft(s[2]),end:Ft(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Ft(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=I.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(gt,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||R}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:R,DataLayers:xe,DataFunctions:ls,Layouts:ns,MatchFunctions:Jt,ScaleFunctions:ee,TransformationFunctions:Z,Widgets:jt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),R}},cs=[];hs.use=function(t,...e){if(!cs.includes(t)){if(e.unshift(hs),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}cs.push(t)}};const ds=hs})(),LocusZoom=i.default})(); +/*! Locuszoom 0.14.0 */ +var LocusZoom;(()=>{var t={483:(t,e,s)=>{"use strict";const i=s(919);t.exports=function(t,...e){if(!t){if(1===e.length&&e[0]instanceof Error)throw e[0];throw new i(e)}}},919:(t,e,s)=>{"use strict";const i=s(820);t.exports=class extends Error{constructor(t){super(t.filter((t=>""!==t)).map((t=>"string"==typeof t?t:t instanceof Error?t.message:i(t))).join(" ")||"Unknown error"),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e.assert)}}},820:t=>{"use strict";t.exports=function(...t){try{return JSON.stringify.apply(null,t)}catch(t){return"[Cannot display object: "+t.message+"]"}}},681:(t,e,s)=>{"use strict";const i=s(483),a={};e.B=class{constructor(){this._items=[],this.nodes=[]}add(t,e){const s=[].concat((e=e||{}).before||[]),a=[].concat(e.after||[]),n=e.group||"?",o=e.sort||0;i(!s.includes(n),`Item cannot come before itself: ${n}`),i(!s.includes("?"),"Item cannot come before unassociated items"),i(!a.includes(n),`Item cannot come after itself: ${n}`),i(!a.includes("?"),"Item cannot come after unassociated items"),Array.isArray(t)||(t=[t]);for(const e of t){const t={seq:this._items.length,sort:o,before:s,after:a,group:n,node:e};this._items.push(t)}if(!e.manual){const t=this._sort();i(t,"item","?"!==n?`added into group ${n}`:"","created a dependencies error")}return this.nodes}merge(t){Array.isArray(t)||(t=[t]);for(const e of t)if(e)for(const t of e._items)this._items.push(Object.assign({},t));this._items.sort(a.mergeSort);for(let t=0;tt.sort===e.sort?0:t.sort{function e(t){if("string"==typeof t.source.flags)return t.source.flags;var e=[];return t.global&&e.push("g"),t.ignoreCase&&e.push("i"),t.multiline&&e.push("m"),t.sticky&&e.push("y"),t.unicode&&e.push("u"),e.join("")}t.exports=function t(s){if("function"==typeof s)return s;var i=Array.isArray(s)?[]:{};for(var a in s){var n=s[a],o={}.toString.call(n).slice(8,-1);i[a]="Array"==o||"Object"==o?t(n):"Date"==o?new Date(n.getTime()):"RegExp"==o?RegExp(n.source,e(n)):n}return i}}},e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={exports:{}};return t[i](a,a.exports,s),a.exports}s.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return s.d(e,{a:e}),e},s.d=(t,e)=>{for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),s.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};(()=>{"use strict";s.d(i,{default:()=>ds});var t={};s.r(t),s.d(t,{AssociationLZ:()=>M,BaseAdapter:()=>$,BaseApiAdapter:()=>z,BaseLZAdapter:()=>k,BaseUMAdapter:()=>E,GeneConstraintLZ:()=>A,GeneLZ:()=>N,GwasCatalogLZ:()=>S,LDServer:()=>O,PheWASLZ:()=>j,RecombLZ:()=>T,StaticSource:()=>L});var e={};s.r(e),s.d(e,{htmlescape:()=>F,is_numeric:()=>H,log10:()=>D,logtoscinotation:()=>U,neglog10:()=>B,scinotation:()=>q,urlencode:()=>G});var a={};s.r(a),s.d(a,{BaseWidget:()=>yt,_Button:()=>ft,display_options:()=>Ot,download:()=>vt,download_png:()=>wt,filter_field:()=>xt,menu:()=>St,move_panel_down:()=>kt,move_panel_up:()=>zt,region_scale:()=>bt,remove_panel:()=>$t,resize_to_data:()=>Nt,set_state:()=>Tt,shift_region:()=>Et,title:()=>mt,toggle_legend:()=>At,zoom_region:()=>Mt});var n={};s.r(n),s.d(n,{categorical_bin:()=>Vt,effect_direction:()=>Qt,if_value:()=>Zt,interpolate:()=>Xt,numerical_bin:()=>Kt,ordinal_cycle:()=>Wt,stable_choice:()=>Yt});var o={};s.r(o),s.d(o,{BaseDataLayer:()=>ie,annotation_track:()=>ne,arcs:()=>he,category_scatter:()=>me,genes:()=>de,highlight_regions:()=>re,line:()=>_e,orthogonal_line:()=>ge,scatter:()=>fe});var r={};s.r(r),s.d(r,{data_layer:()=>es,panel:()=>ss,plot:()=>is,toolbar:()=>ts,toolbar_widgets:()=>Qe,tooltip:()=>Xe});const l="0.14.0";class h{constructor(){this._items=new Map}get(t){if(!this._items.has(t))throw new Error(`Item not found: ${t}`);return this._items.get(t)}add(t,e,s=!1){if(!s&&this._items.has(t))throw new Error(`Item ${t} is already defined`);return this._items.set(t,e),e}remove(t){return this._items.delete(t)}has(t){return this._items.has(t)}list(){return Array.from(this._items.keys())}}class c extends h{create(t,...e){return new(this.get(t))(...e)}extend(t,e,s){if(console.warn("Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses"),3!==arguments.length)throw new Error("Invalid arguments to .extend");const i=this.get(t);class a extends i{}return Object.assign(a.prototype,s,i),this.add(e,a),a}}class d{constructor(t,e,s={},i=null,a=null){this.key=t,this.value=e,this.metadata=s,this.prev=i,this.next=a}}class u{constructor(t=3){if(this._max_size=t,this._cur_size=0,this._store=new Map,this._head=null,this._tail=null,null===t||t<0)throw new Error('Cache "max_size" must be >= 0')}has(t){return this._store.has(t)}get(t){const e=this._store.get(t);return e?(this._head!==e&&this.add(t,e.value),e.value):null}add(t,e,s={}){if(0===this._max_size)return;const i=this._store.get(t);i&&this._remove(i);const a=new d(t,e,s,null,this._head);if(this._head?this._head.prev=a:this._tail=a,this._head=a,this._store.set(t,a),this._max_size>=0&&this._cur_size>=this._max_size){const t=this._tail;this._tail=this._tail.prev,this._remove(t)}this._cur_size+=1}clear(){this._head=null,this._tail=null,this._cur_size=0,this._store=new Map}remove(t){const e=this._store.get(t);return!!e&&(this._remove(e),!0)}_remove(t){null!==t.prev?t.prev.next=t.next:this._head=t.next,null!==t.next?t.next.prev=t.prev:this._tail=t.prev,this._store.delete(t.key),this._cur_size-=1}find(t){let e=this._head;for(;e;){const s=e.next;if(t(e))return e;e=s}}}var _=s(957),p=s.n(_);function g(t){return"object"!=typeof t?t:p()(t)}var y=s(681);function f(t,e,s,i=!0){if(!s.length)return[];const a=s.map((t=>function(t){const e=/^(?\w+)$|((?\w+)+\(\s*(?[^)]+?)\s*\))/.exec(t);if(!e)throw new Error(`Unable to parse dependency specification: ${t}`);let{name_alone:s,name_deps:i,deps:a}=e.groups;return s?[s,[]]:(a=a.split(/\s*,\s*/),[i,a])}(t))),n=new Map(a),o=new y.B;for(let[t,e]of n.entries())try{o.add(t,{after:e,group:t})}catch(e){throw new Error(`Invalid or possible circular dependency specification for: ${t}`)}const r=o.nodes,l=new Map;for(let s of r){const i=e.get(s);if(!i)throw new Error(`Data has been requested from source '${s}', but no matching source was provided`);const a=n.get(s)||[],o=Promise.all(a.map((t=>l.get(t)))).then((e=>{const a=Object.assign({_provider_name:s},t);return i.getData(a,...e)}));l.set(s,o)}return Promise.all([...l.values()]).then((t=>i?t[t.length-1]:t))}function m(t,e){const s=new Map;for(let i of t){const t=i[e];if(void 0===t)throw new Error(`All records must specify a value for the field "${e}"`);if("object"==typeof t)throw new Error("Attempted to group on a field with non-primitive values");let a=s.get(t);a||(a=[],s.set(t,a)),a.push(i)}return s}function b(t,e,s,i,a){const n=m(s,a),o=[];for(let s of e){const e=s[i],a=n.get(e)||[];a.length?o.push(...a.map((t=>Object.assign({},g(t),g(s))))):"inner"!==t&&o.push(g(s))}if("outer"===t){const t=m(e,i);for(let e of s){const s=e[a];(t.get(s)||[]).length||o.push(g(e))}}return o}function x(t,e,s,i){return b("left",...arguments)}const v=/^(?:chr)?([a-zA-Z0-9]+?)[_:-](\d+)[_:|-]?(\w+)?[/_:|-]?([^_]+)?_?(.*)?/;function w(t,e=!1){const s=t&&t.match(v);if(s)return s.slice(1);if(e)return null;throw new Error(`Could not understand marker format for ${t}. Should be of format chr:pos or chr:pos_ref/alt`)}class ${constructor(){throw new Error('The "BaseAdapter" and "BaseApiAdapter" classes have been replaced in LocusZoom 0.14. See migration guide for details.')}}class z extends ${}class k extends class extends class{constructor(t={}){this._config=t;const{cache_enabled:e=!0,cache_size:s=3}=t;this._enable_cache=e,this._cache=new u(s)}_buildRequestOptions(t,e){return Object.assign({},t)}_getCacheKey(t){if(this._enable_cache)throw new Error("Method not implemented");return null}_performRequest(t){throw new Error("Not implemented")}_normalizeResponse(t,e){return t}_annotateRecords(t,e){return t}_postProcessResponse(t,e){return t}getData(t={},...e){t=this._buildRequestOptions(t,...e);const s=this._getCacheKey(t);let i;return this._enable_cache&&this._cache.has(s)?i=this._cache.get(s):(i=Promise.resolve(this._performRequest(t)).then((e=>this._normalizeResponse(e,t))),this._cache.add(s,i,t._cache_meta),i.catch((t=>this._cache.remove(s)))),i.then((t=>g(t))).then((e=>this._annotateRecords(e,t))).then((e=>this._postProcessResponse(e,t)))}}{constructor(t={}){super(t),this._url=t.url}_getCacheKey(t){return this._getURL(t)}_getURL(t){return this._url}_performRequest(t){const e=this._getURL(t);if(!this._url)throw new Error('Web based resources must specify a resource URL as option "url"');return fetch(e).then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()}))}_normalizeResponse(t,e){return"string"==typeof t?JSON.parse(t):t}}{constructor(t={}){t.params&&(console.warn('Deprecation warning: all options in "config.params" should now be specified as top level keys.'),Object.assign(t,t.params||{}),delete t.params),super(t);const{prefix_namespace:e=!0,limit_fields:s}=t;this._prefix_namespace=e,this._limit_fields=!!s&&new Set(s)}_getCacheKey(t){let{chr:e,start:s,end:i}=t;const a=this._cache.find((({metadata:t})=>e===t.chr&&s>=t.start&&i<=t.end));return a&&({chr:e,start:s,end:i}=a.metadata),t._cache_meta={chr:e,start:s,end:i},`${e}_${s}_${i}`}_postProcessResponse(t,e){if(!this._prefix_namespace||!Array.isArray(t))return t;const{_limit_fields:s}=this,{_provider_name:i}=e;return t.map((t=>Object.entries(t).reduce(((t,[e,a])=>(s&&!s.has(e)||(t[`${i}:${e}`]=a),t)),{})))}_findPrefixedKey(t,e){const s=new RegExp(`:${e}$`),i=Object.keys(t).find((t=>s.test(t)));if(!i)throw new Error(`Could not locate the required key name: ${e} in dependent data`);return i}}class E extends k{constructor(t={}){super(t),this._genome_build=t.genome_build||t.build}_validateBuildSource(t,e){if(t&&e||!t&&!e)throw new Error(`${this.constructor.name} must provide a parameter specifying either "build" or "source". It should not specify both.`);if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`)}_normalizeResponse(t,e){let s=super._normalizeResponse(...arguments);if(s=s.data||s,Array.isArray(s))return s;const i=Object.keys(s),a=s[i[0]].length;if(!i.every((function(t){return s[t].length===a})))throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);const n=[],o=Object.keys(s);for(let t=0;t25||"GRCh38"===s)return Promise.resolve([]);e=`{${e.join(" ")} }`;const i=this._getURL(t),a=JSON.stringify({query:e});return fetch(i,{method:"POST",body:a,headers:{"Content-Type":"application/json"}}).then((t=>t.ok?t.text():[])).catch((t=>[]))}_normalizeResponse(t){if("string"!=typeof t)return t;return JSON.parse(t).data}}class O extends E{constructor(t){t.limit_fields||(t.limit_fields=["variant2","position2","correlation"]),super(t)}__find_ld_refvar(t,e){const s=this._findPrefixedKey(e[0],"variant"),i=this._findPrefixedKey(e[0],"log_pvalue");let a,n={};if(t.ldrefvar)a=t.ldrefvar,n=e.find((t=>t[s]===a))||{};else{let t=0;for(let o of e){const{[s]:e,[i]:r}=o;r>t&&(t=r,a=e,n=o)}}n.lz_is_ld_refvar=!0;const o=w(a,!0);if(!o)throw new Error("Could not request LD for a missing or incomplete marker format");const[r,l,h,c]=o;a=`${r}:${l}`,h&&c&&(a+=`_${h}/${c}`);const d=+l;return d&&t.ldrefvar&&t.chr&&(r!==String(t.chr)||dt.end)?(t.ldrefvar=null,this.__find_ld_refvar(t,e)):a}_buildRequestOptions(t,e){if(!e)throw new Error("LD request must depend on association data");const s=super._buildRequestOptions(...arguments);if(!e.length)return s._skip_request=!0,s;s.ld_refvar=this.__find_ld_refvar(t,e);const i=t.genome_build||this._config.build||"GRCh37";let a=t.ld_source||this._config.source||"1000G";const n=t.ld_pop||this._config.population||"ALL";return"1000G"===a&&"GRCh38"===i&&(a="1000G-FRZ09"),this._validateBuildSource(i,null),Object.assign({},s,{genome_build:i,ld_source:a,ld_population:n})}_getURL(t){const e=this._config.method||"rsquare",{chr:s,start:i,end:a,ld_refvar:n,genome_build:o,ld_source:r,ld_population:l}=t;return[super._getURL(t),"genome_builds/",o,"/references/",r,"/populations/",l,"/variants","?correlation=",e,"&variant=",encodeURIComponent(n),"&chrom=",encodeURIComponent(s),"&start=",encodeURIComponent(i),"&stop=",encodeURIComponent(a)].join("")}_getCacheKey(t){const e=super._getCacheKey(t),{ld_refvar:s,ld_source:i,ld_population:a}=t;return`${e}_${s}_${i}_${a}`}_performRequest(t){if(t._skip_request)return Promise.resolve([]);const e=this._getURL(t);let s={data:{}},i=function(t){return fetch(t).then().then((t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})).then((function(t){return t=JSON.parse(t),Object.keys(t.data).forEach((function(e){s.data[e]=(s.data[e]||[]).concat(t.data[e])})),t.next?i(t.next):s}))};return i(e)}}class T extends E{constructor(t){t.limit_fields||(t.limit_fields=["position","recomb_rate"]),super(t)}_getURL(t){const e=t.genome_build||this._config.build;let s=this._config.source;this._validateBuildSource(e,s);const i=e?`&build=${e}`:` and id in ${s}`;return`${super._getURL(t)}?filter=chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}${i}`}}class L extends k{constructor(t={}){super(...arguments);const{data:e}=t;if(!e||Array.isArray(t))throw new Error("'StaticSource' must provide data as required option 'config.data'");this._data=e}_performRequest(t){return Promise.resolve(this._data)}}class j extends E{_getURL(t){const e=(t.genome_build?[t.genome_build]:null)||this._config.build;if(!e||!Array.isArray(e)||!e.length)throw new Error(["Adapter",this.constructor.name,"requires that you specify array of one or more desired genome build names"].join(" "));return[super._getURL(t),"?filter=variant eq '",encodeURIComponent(t.variant),"'&format=objects&",e.map((function(t){return`build=${encodeURIComponent(t)}`})).join("&")].join("")}_getCacheKey(t){return this._getURL(t)}}const P=new c;for(let[e,s]of Object.entries(t))P.add(e,s);P.add("StaticJSON",L),P.add("LDLZ2",O);const R=P,I=d3,C={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function D(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function B(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function U(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),s=e-t,i=Math.pow(10,s);return 1===e?(i/10).toFixed(4):2===e?(i/100).toFixed(3):`${i.toFixed(2)} × 10^-${e}`}function q(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let s;return s=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(s)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function F(t){return t?(t=`${t}`).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function H(t){return"number"==typeof t}function G(t){return encodeURIComponent(t)}const J=new class extends h{_collectTransforms(t){const e=t.match(/\|([^|]+)/g).map((t=>super.get(t.substring(1))));return t=>e.reduce(((t,e)=>e(t)),t)}get(t){return t?"|"===t.substring(0,1)?this._collectTransforms(t):super.get(t):null}};for(let[t,s]of Object.entries(e))J.add(t,s);const Z=J;class K{constructor(t){if(!/^(?:\w+:\w+|^\w+)(?:\|\w+)*$/.test(t))throw new Error(`Invalid field specifier: '${t}'`);const[e,...s]=t.split("|");this.full_name=t,this.field_name=e,this.transformations=s.map((t=>Z.get(t)))}_applyTransformations(t){return this.transformations.forEach((function(e){t=e(t)})),t}resolve(t,e){if(void 0===t[this.full_name]){let s=null;void 0!==t[this.field_name]?s=t[this.field_name]:e&&void 0!==e[this.field_name]&&(s=e[this.field_name]),t[this.full_name]=this._applyTransformations(s)}return t[this.full_name]}}const V=/^(\*|[\w]+)/,W=/^\[\?\(@((?:\.[\w]+)+) *===? *([0-9.eE-]+|"[^"]*"|'[^']*')\)\]/;function Y(t){if(".."===t.substr(0,2)){if("["===t[2])return{text:"..",attr:"*",depth:".."};const e=V.exec(t.substr(2));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dotdot_attr.`;return{text:`..${e[0]}`,attr:e[1],depth:".."}}if("."===t[0]){const e=V.exec(t.substr(1));if(!e)throw`Cannot parse ${JSON.stringify(t)} as dot_attr.`;return{text:`.${e[0]}`,attr:e[1],depth:"."}}if("["===t[0]){const e=W.exec(t);if(!e)throw`Cannot parse ${JSON.stringify(t)} as expr.`;let s;try{s=JSON.parse(e[2])}catch(t){s=JSON.parse(e[2].replace(/^'|'$/g,'"'))}return{text:e[0],attrs:e[1].substr(1).split("."),value:s}}throw`The query ${JSON.stringify(t)} doesn't look valid.`}function X(t,e){let s;for(let i of e)s=t,t=t[i];return[s,e[e.length-1],t]}function Q(t,e){if(!e.length)return[[]];const s=e[0],i=e.slice(1);let a=[];if(s.attr&&"."===s.depth&&"*"!==s.attr){const n=t[s.attr];1===e.length?void 0!==n&&a.push([s.attr]):a.push(...Q(n,i).map((t=>[s.attr].concat(t))))}else if(s.attr&&"."===s.depth&&"*"===s.attr)for(let[e,s]of Object.entries(t))a.push(...Q(s,i).map((t=>[e].concat(t))));else if(s.attr&&".."===s.depth){if("object"==typeof t&&null!==t){"*"!==s.attr&&s.attr in t&&a.push(...Q(t[s.attr],i).map((t=>[s.attr].concat(t))));for(let[n,o]of Object.entries(t))a.push(...Q(o,e).map((t=>[n].concat(t)))),"*"===s.attr&&a.push(...Q(o,i).map((t=>[n].concat(t))))}}else if(s.attrs)for(let[e,n]of Object.entries(t)){const[t,o,r]=X(n,s.attrs);r===s.value&&a.push(...Q(n,i).map((t=>[e].concat(t))))}const n=(o=a,r=JSON.stringify,[...new Map(o.map((t=>[r(t),t]))).values()]);var o,r;return n.sort(((t,e)=>e.length-t.length||JSON.stringify(t).localeCompare(JSON.stringify(e)))),n}function tt(t,e){const s=function(t,e){let s=[];for(let i of Q(t,e))s.push(X(t,i));return s}(t,function(t){t=function(t){return t?(["$","["].includes(t[0])||(t=`$.${t}`),"$"===t[0]&&(t=t.substr(1)),t):""}(t);let e=[];for(;t.length;){const s=Y(t);t=t.substr(s.text.length),e.push(s)}return e}(e));return s.length||console.warn(`No items matched the specified query: '${e}'`),s}const et=Math.sqrt(3),st={draw(t,e){const s=-Math.sqrt(e/(3*et));t.moveTo(0,2*-s),t.lineTo(-et*s,s),t.lineTo(et*s,s),t.closePath()}};function it(t,e){if(e=e||{},!t||"object"!=typeof t||"object"!=typeof e)throw new Error("Layout and shared namespaces must be provided as objects");for(let[s,i]of Object.entries(t))"namespace"===s?Object.keys(i).forEach((t=>{const s=e[t];s&&(i[t]=s)})):null!==i&&"object"==typeof i&&(t[s]=it(i,e));return t}function at(t,e){if("object"!=typeof t||"object"!=typeof e)throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof t}, ${typeof e} given`);for(let s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let i=null===t[s]?"undefined":typeof t[s],a=typeof e[s];if("object"===i&&Array.isArray(t[s])&&(i="array"),"object"===a&&Array.isArray(e[s])&&(a="array"),"function"===i||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==i?"object"!==i||"object"!==a||(t[s]=at(t[s],e[s])):t[s]=nt(e[s])}return t}function nt(t){return JSON.parse(JSON.stringify(t))}function ot(t){if(!t)return null;if("triangledown"===t)return st;const e=`symbol${t.charAt(0).toUpperCase()+t.slice(1)}`;return I[e]||null}function rt(t,e,s=null){const i=new Set;if(!s){if(!e.length)return i;const t=e.join("|");s=new RegExp(`(?:{{)?(?:#if *)?((?:${t}):\\w+)`,"g")}for(const a of Object.values(t)){const t=typeof a;let n=[];if("string"===t){let t;for(;null!==(t=s.exec(a));)n.push(t[1])}else{if(null===a||"object"!==t)continue;n=rt(a,e,s)}for(let t of n)i.add(t)}return i}function lt(t,e,s,i=!0){const a=typeof t;if(Array.isArray(t))return t.map((t=>lt(t,e,s,i)));if("object"===a&&null!==t)return Object.keys(t).reduce(((a,n)=>(a[n]=lt(t[n],e,s,i),a)),{});if("string"!==a)return t;{const a=e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&");if(i){const e=new RegExp(`${a}\\|\\w+`,"g");(t.match(e)||[]).forEach((t=>console.warn(`renameFields is renaming a field that uses transform functions: was '${t}' . Verify that these transforms are still appropriate.`)))}const n=new RegExp(`${a}(?!\\w+)`,"g");return t.replace(n,s)}}function ht(t,e,s){return function(t,e,s){return tt(t,e).map((([t,e,i])=>{const a="function"==typeof s?s(i):s;return t[e]=a,a}))}(t,e,s)}function ct(t,e){return function(t,e){return tt(t,e).map((t=>t[2]))}(t,e)}class dt{constructor(t,e,s){this._callable=ls.get(t),this._initiator=e,this._params=s||[]}getData(t,...e){const s={plot_state:t,data_layer:this._initiator};return Promise.resolve(this._callable(s,e,...this._params))}}const ut=class{constructor(t){this._sources=t}config_to_sources(t={},e=[],s){const i=new Map,a=Object.keys(t);let n=e.find((t=>"fetch"===t.type));n||(n={type:"fetch",from:a},e.unshift(n));const o=/^\w+$/;for(let[e,s]of Object.entries(t)){if(!o.test(e))throw new Error(`Invalid namespace name: '${e}'. Must contain only alphanumeric characters`);const t=this._sources.get(s);if(!t)throw new Error(`A data layer has requested an item not found in DataSources: data type '${e}' from ${s}`);i.set(e,t),n.from.find((t=>t.split("(")[0]===e))||n.from.push(e)}let r=Array.from(n.from);for(let t of e){let{type:e,name:a,requires:n,params:o}=t;if("fetch"!==e){let l=0;if(a||(a=t.name=`join${l}`,l+=1),i.has(a))throw new Error(`Configuration error: within a layer, join name '${a}' must be unique`);n.forEach((t=>{if(!i.has(t))throw new Error(`Data operation cannot operate on unknown provider '${t}'`)}));const h=new dt(e,s,o);i.set(a,h),r.push(`${a}(${n.join(", ")})`)}}return[i,r]}getData(t,e,s){return s.length?f(t,e,s,!0):Promise.resolve([])}};function _t(){return{showing:!1,selector:null,content_selector:null,hide_delay:null,show:(t,e)=>(this.curtain.showing||(this.curtain.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-curtain").attr("id",`${this.id}.curtain`),this.curtain.content_selector=this.curtain.selector.append("div").attr("class","lz-curtain-content"),this.curtain.selector.append("div").attr("class","lz-curtain-dismiss").html("Dismiss").on("click",(()=>this.curtain.hide())),this.curtain.showing=!0),this.curtain.update(t,e)),update:(t,e)=>{if(!this.curtain.showing)return this.curtain;clearTimeout(this.curtain.hide_delay),"object"==typeof e&>(this.curtain.selector,e);const s=this._getPageOrigin(),i=this.layout.height||this._total_height;return this.curtain.selector.style("top",`${s.y}px`).style("left",`${s.x}px`).style("width",`${this.parent_plot.layout.width}px`).style("height",`${i}px`),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",i-40+"px"),"string"==typeof t&&this.curtain.content_selector.html(t),this.curtain},hide:t=>this.curtain.showing?"number"==typeof t?(clearTimeout(this.curtain.hide_delay),this.curtain.hide_delay=setTimeout(this.curtain.hide,t),this.curtain):(this.curtain.selector.remove(),this.curtain.selector=null,this.curtain.content_selector=null,this.curtain.showing=!1,this.curtain):this.curtain}}function pt(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=I.select(this.parent_plot.svg.node().parentNode).insert("div").attr("class","lz-loader").attr("id",`${this.id}.loader`),this.loader.content_selector=this.loader.selector.append("div").attr("class","lz-loader-content"),this.loader.progress_selector=this.loader.selector.append("div").attr("class","lz-loader-progress-container").append("div").attr("class","lz-loader-progress"),this.loader.showing=!0,void 0===t&&(t="Loading...")),this.loader.update(t)),update:(t,e)=>{if(!this.loader.showing)return this.loader;clearTimeout(this.loader.hide_delay),"string"==typeof t&&this.loader.content_selector.html(t);const s=this._getPageOrigin(),i=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",s.y+this.layout.height-i.height-6+"px").style("left",`${s.x+6}px`),"number"==typeof e&&this.loader.progress_selector.style("width",`${Math.min(Math.max(e,1),100)}%`),this.loader},animate:()=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!0),this.loader),setPercentCompleted:t=>(this.loader.progress_selector.classed("lz-loader-progress-animated",!1),this.loader.update(null,t)),hide:t=>this.loader.showing?"number"==typeof t?(clearTimeout(this.loader.hide_delay),this.loader.hide_delay=setTimeout(this.loader.hide,t),this.loader):(this.loader.selector.remove(),this.loader.selector=null,this.loader.content_selector=null,this.loader.progress_selector=null,this.loader.cancel_selector=null,this.loader.showing=!1,this.loader):this.loader}}function gt(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class yt{constructor(t,e){this.layout=t||{},this.layout.color||(this.layout.color="gray"),this.parent=e||null,this.parent_panel=null,this.parent_plot=null,this.parent_svg=null,this.parent&&("panel"===this.parent.type?(this.parent_panel=this.parent.parent,this.parent_plot=this.parent.parent.parent,this.parent_svg=this.parent_panel):(this.parent_plot=this.parent.parent,this.parent_svg=this.parent_plot)),this.selector=null,this.button=null,this.persist=!1,this.layout.position||(this.layout.position="left")}show(){if(this.parent&&this.parent.selector){if(!this.selector){const t=["start","middle","end"].includes(this.layout.group_position)?` lz-toolbar-group-${this.layout.group_position}`:"";this.selector=this.parent.selector.append("div").attr("class",`lz-toolbar-${this.layout.position}${t}`),this.layout.style&>(this.selector,this.layout.style),"function"==typeof this.initialize&&this.initialize()}return this.button&&"highlighted"===this.button.status&&this.button.menu.show(),this.selector.style("visibility","visible"),this.update(),this.position()}}update(){}position(){return this.button&&this.button.menu.position(),this}shouldPersist(){return!!this.persist||!(!this.button||!this.button.persist)}hide(){return!this.selector||this.shouldPersist()||(this.button&&this.button.menu.hide(),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.button&&this.button.menu&&this.button.menu.destroy(),this.selector.remove(),this.selector=null,this.button=null),this):this}}class ft{constructor(t){if(!(t instanceof yt))throw new Error("Unable to create toolbar widget button, invalid parent");this.parent=t,this.parent_panel=this.parent.parent_panel,this.parent_plot=this.parent.parent_plot,this.parent_svg=this.parent.parent_svg,this.parent_toolbar=this.parent.parent,this.selector=null,this.tag="a",this.html="",this.title="",this.color="gray",this.style={},this.persist=!1,this.permanent=!1,this.status="",this.menu={outer_selector:null,inner_selector:null,scroll_position:0,hidden:!0,show:()=>(this.menu.outer_selector||(this.menu.outer_selector=I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class",`lz-toolbar-menu lz-toolbar-menu-${this.color}`).attr("id",`${this.parent_svg.getBaseId()}.toolbar.menu`),this.menu.inner_selector=this.menu.outer_selector.append("div").attr("class","lz-toolbar-menu-content"),this.menu.inner_selector.on("scroll",(()=>{this.menu.scroll_position=this.menu.inner_selector.node().scrollTop}))),this.menu.outer_selector.style("visibility","visible"),this.menu.hidden=!1,this.menu.update()),update:()=>this.menu.outer_selector?(this.menu.populate(),this.menu.inner_selector&&(this.menu.inner_selector.node().scrollTop=this.menu.scroll_position),this.menu.position()):this.menu,position:()=>{if(!this.menu.outer_selector)return this.menu;this.menu.outer_selector.style("height",null);const t=this.parent_svg._getPageOrigin(),e=document.documentElement.scrollTop||document.body.scrollTop,s=this.parent_plot.getContainerOffset(),i=this.parent_toolbar.selector.node().getBoundingClientRect(),a=this.selector.node().getBoundingClientRect(),n=this.menu.outer_selector.node().getBoundingClientRect(),o=this.menu.inner_selector.node().scrollHeight;let r,l;"panel"===this.parent_toolbar.type?(r=t.y+i.height+6,l=Math.max(t.x+this.parent_plot.layout.width-n.width-3,t.x+3)):(r=a.bottom+e+3-s.top,l=Math.max(a.left+a.width-n.width-s.left,t.x+3));const h=Math.max(this.parent_plot.layout.width-6-20,20),c=h,d=h-12,u=Math.max(this.parent_svg.layout.height-30-14,14),_=Math.min(o+14,u);return this.menu.outer_selector.style("top",`${r}px`).style("left",`${l}px`).style("max-width",`${c}px`).style("max-height",`${u}px`).style("height",`${_}px`),this.menu.inner_selector.style("max-width",`${d}px`),this.menu.inner_selector.node().scrollTop=this.menu.scroll_position,this.menu},hide:()=>this.menu.outer_selector?(this.menu.outer_selector.style("visibility","hidden"),this.menu.hidden=!0,this.menu):this.menu,destroy:()=>this.menu.outer_selector?(this.menu.inner_selector.remove(),this.menu.outer_selector.remove(),this.menu.inner_selector=null,this.menu.outer_selector=null,this.menu):this.menu,populate:()=>{throw new Error("Method must be implemented")},setPopulate:t=>("function"==typeof t?(this.menu.populate=t,this.setOnclick((()=>{this.menu.hidden?(this.menu.show(),this.highlight().update(),this.persist=!0):(this.menu.hide(),this.highlight(!1).update(),this.permanent||(this.persist=!1))}))):this.setOnclick(),this)}}setColor(t){return void 0!==t&&(["gray","red","orange","yellow","green","blue","purple"].includes(t)?this.color=t:this.color="gray"),this}setPermanent(t){return t=void 0===t||Boolean(t),this.permanent=t,this.permanent&&(this.persist=!0),this}shouldPersist(){return this.permanent||this.persist}setStyle(t){return void 0!==t&&(this.style=t),this}getClass(){const t=["start","middle","end"].includes(this.parent.layout.group_position)?` lz-toolbar-button-group-${this.parent.layout.group_position}`:"";return`lz-toolbar-button lz-toolbar-button-${this.color}${this.status?`-${this.status}`:""}${t}`}setStatus(t){return void 0!==t&&["","highlighted","disabled"].includes(t)&&(this.status=t),this.update()}highlight(t){return(t=void 0===t||Boolean(t))?this.setStatus("highlighted"):"highlighted"===this.status?this.setStatus(""):this}disable(t){return(t=void 0===t||Boolean(t))?this.setStatus("disabled"):"disabled"===this.status?this.setStatus(""):this}onmouseover(){}setOnMouseover(t){return this.onmouseover="function"==typeof t?t:function(){},this}onmouseout(){}setOnMouseout(t){return this.onmouseout="function"==typeof t?t:function(){},this}onclick(){}setOnclick(t){return this.onclick="function"==typeof t?t:function(){},this}setTitle(t){return void 0!==t&&(this.title=t.toString()),this}setHtml(t){return void 0!==t&&(this.html=t.toString()),this}show(){if(this.parent)return this.selector||(this.selector=this.parent.selector.append(this.tag).attr("class",this.getClass())),this.update()}preUpdate(){return this}update(){return this.selector?(this.preUpdate(),this.selector.attr("class",this.getClass()).attr("title",this.title).on("mouseover","disabled"===this.status?null:this.onmouseover).on("mouseout","disabled"===this.status?null:this.onmouseout).on("click","disabled"===this.status?null:this.onclick).html(this.html).call(gt,this.style),this.menu.update(),this.postUpdate(),this):this}postUpdate(){return this}hide(){return this.selector&&!this.shouldPersist()&&(this.selector.remove(),this.selector=null),this}}class mt extends yt{show(){return this.div_selector||(this.div_selector=this.parent.selector.append("div").attr("class",`lz-toolbar-title lz-toolbar-${this.layout.position}`),this.title_selector=this.div_selector.append("h3")),this.update()}update(){let t=this.layout.title.toString();return this.layout.subtitle&&(t+=` ${this.layout.subtitle}`),this.title_selector.html(t),this}}class bt extends yt{update(){return isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end)||null===this.parent_plot.state.start||null===this.parent_plot.state.end?this.selector.style("display","none"):(this.selector.style("display",null),this.selector.html(qt(this.parent_plot.state.end-this.parent_plot.state.start,null,!0))),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&>(this.selector,this.layout.style),this}}class xt extends yt{constructor(t,e){if(super(t,e),!this.parent_panel)throw new Error("Filter widget can only be used in panel toolbars");if(this._data_layer=this.parent_panel.data_layers[t.layer_name],!this._data_layer)throw new Error(`Filter widget could not locate the specified layer_name: '${t.layer_name}'`);if(this._event_name=t.custom_event_name||"widget_filter_field_action",this._field=t.field,this._field_display_html=t.field_display_html,this._operator=t.operator,this._filter_id=null,this._data_type=t.data_type||"number",!["number","string"].includes(this._data_type))throw new Error("Filter must be either string or number");this._value_selector=null}_getTarget(){this._data_layer.layout.filters||(this._data_layer.layout.filters=[]);let t=this._data_layer.layout.filters.find((t=>t.field===this._field&&t.operator===this._operator&&(!this._filter_id||t.id===this._filter_id)));return t||(t={field:this._field,operator:this._operator,value:null},this._filter_id&&(t.id=this._filter_id),this._data_layer.layout.filters.push(t)),t}_clearFilter(){if(this._data_layer.layout.filters){const t=this._data_layer.layout.filters.indexOf(this._getTarget());this._data_layer.layout.filters.splice(t,1)}}_setFilter(t){if(null===t)this._value_selector.style("border","1px solid red").style("color","red"),this._clearFilter();else{this._getTarget().value=t}this.parent_svg.emit(this._event_name,{field:this._field,operator:this._operator,value:t,filter_id:this._filter_id},!0)}_getValue(){let t=this._value_selector.property("value");return null===t||""===t||"number"===this._data_type&&(t=+t,Number.isNaN(t))?null:t}update(){this._value_selector||(this.selector.style("padding","0 6px"),this.selector.append("span").html(this._field_display_html).style("background","#fff").style("padding-left","3px"),this.selector.append("span").text(this._operator).style("padding","0 3px").style("background","#fff"),this._value_selector=this.selector.append("input").attr("size",this.layout.input_size||4).on("input",function(t,e=500){let s;return()=>{clearTimeout(s),s=setTimeout((()=>t.apply(this,arguments)),e)}}((()=>{this._value_selector.style("border",null).style("color",null);const t=this._getValue();this._setFilter(t),this.parent_panel.render()}),750)))}}class vt extends yt{constructor(t,e){super(t,e),this._filename=this.layout.filename||"locuszoom.svg",this._button_html=this.layout.button_html||"Save SVG",this._button_title=this.layout.button_title||"Download hi-res image",this._event_name=t.custom_event_name||"widget_save_svg"}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this._button_html).setTitle(this._button_title).setOnMouseover((()=>{this.button.selector.classed("lz-toolbar-button-gray-disabled",!0).html("Preparing Image"),this._getBlobUrl().then((t=>{const e=this.button.selector.attr("href");e&&URL.revokeObjectURL(e),this.button.selector.attr("href",t).classed("lz-toolbar-button-gray-disabled",!1).classed("lz-toolbar-button-gray-highlighted",!0).html(this._button_html)}))})).setOnMouseout((()=>{this.button.selector.classed("lz-toolbar-button-gray-highlighted",!1)})),this.button.show(),this.button.selector.attr("href-lang","image/svg+xml").attr("download",this._filename).on("click",(()=>this.parent_svg.emit(this._event_name,{filename:this._filename},!0)))),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let s="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=I.select(e),e.selectAll("g.lz-curtain").remove(),e.selectAll("g.lz-mouse_guide").remove(),e.selectAll("g.tick text").each((function(){const t=10*+I.select(this).attr("dy").substring(-2).slice(0,-2);I.select(this).attr("dy",t)}));const s=new XMLSerializer;e=e.node();const[i,a]=this._getDimensions();e.setAttribute("width",i),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(s.serializeToString(e))}))}_getBlobUrl(){return this._generateSVG().then((t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)}))}}class wt extends vt{constructor(t,e){super(...arguments),this._filename=this.layout.filename||"locuszoom.png",this._button_html=this.layout.button_html||"Save PNG",this._button_title=this.layout.button_title||"Download image",this._event_name=t.custom_event_name||"widget_save_png"}_getBlobUrl(){return super._getBlobUrl().then((t=>{const e=document.createElement("canvas"),s=e.getContext("2d"),[i,a]=this._getDimensions();return e.width=i,e.height=a,new Promise(((n,o)=>{const r=new Image;r.onload=()=>{s.drawImage(r,0,0,i,a),URL.revokeObjectURL(t),e.toBlob((t=>{n(URL.createObjectURL(t))}))},r.src=t}))}))}}class $t extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml("×").setTitle("Remove panel").setOnclick((()=>{if(!this.layout.suppress_confirm&&!confirm("Are you sure you want to remove this panel? This cannot be undone."))return!1;const t=this.parent_panel;return t.toolbar.hide(!0),I.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),I.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)})),this.button.show()),this}}class zt extends yt{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick((()=>{this.parent_panel.moveUp(),this.update()})),this.button.show(),this.update()}}class kt extends yt{update(){if(this.button){const t=this.parent_panel.layout.y_index===this.parent_plot._panel_ids_by_y_index.length-1;return this.button.disable(t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick((()=>{this.parent_panel.moveDown(),this.update()})),this.button.show(),this.update()}}class Et extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=5e4),"string"!=typeof t.button_html&&(t.button_html=t.step>0?">":"<"),"string"!=typeof t.button_title&&(t.button_title=`Shift region by ${t.step>0?"+":"-"}${qt(Math.abs(t.step),null,!0)}`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add shift_region toolbar widget: plot state does not have region bounds")}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start+this.layout.step,1),end:this.parent_plot.state.end+this.layout.step})})),this.button.show()),this}}class Mt extends yt{constructor(t,e){if((isNaN(t.step)||0===t.step)&&(t.step=.2),"string"!=typeof t.button_html&&(t.button_html=t.step>0?"z–":"z+"),"string"!=typeof t.button_title&&(t.button_title=`Zoom region ${t.step>0?"out":"in"} by ${(100*Math.abs(t.step)).toFixed(1)}%`),super(t,e),isNaN(this.parent_plot.state.start)||isNaN(this.parent_plot.state.end))throw new Error("Unable to add zoom_region toolbar widget: plot state does not have region bounds")}update(){if(this.button){let t=!0;const e=this.parent_plot.state.end-this.parent_plot.state.start;return this.layout.step>0&&!isNaN(this.parent_plot.layout.max_region_scale)&&e>=this.parent_plot.layout.max_region_scale&&(t=!1),this.layout.step<0&&!isNaN(this.parent_plot.layout.min_region_scale)&&e<=this.parent_plot.layout.min_region_scale&&(t=!1),this.button.disable(!t),this}return this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title).setOnclick((()=>{const t=this.parent_plot.state.end-this.parent_plot.state.start;let e=t*(1+this.layout.step);isNaN(this.parent_plot.layout.max_region_scale)||(e=Math.min(e,this.parent_plot.layout.max_region_scale)),isNaN(this.parent_plot.layout.min_region_scale)||(e=Math.max(e,this.parent_plot.layout.min_region_scale));const s=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-s,1),end:this.parent_plot.state.end+s})})),this.button.show(),this}}class St extends yt{update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html).setTitle(this.layout.button_title),this.button.menu.setPopulate((()=>{this.button.menu.inner_selector.html(this.layout.menu_html)})),this.button.show()),this}}class Nt extends yt{constructor(t){super(...arguments)}update(){return this.button||(this.button=new ft(this).setColor(this.layout.color).setHtml(this.layout.button_html||"Resize to Data").setTitle(this.layout.button_title||"Automatically resize this panel to show all data available").setOnclick((()=>{this.parent_panel.scaleHeightToData(),this.update()})),this.button.show()),this}}class At extends yt{update(){const t=this.parent_panel.legend.layout.hidden?"Show Legend":"Hide Legend";return this.button?(this.button.setHtml(t).show(),this.parent.position(),this):(this.button=new ft(this).setColor(this.layout.color).setTitle("Show or hide the legend for this panel").setOnclick((()=>{this.parent_panel.legend.layout.hidden=!this.parent_panel.legend.layout.hidden,this.parent_panel.legend.render(),this.update()})),this.update())}}class Ot extends yt{constructor(t,e){"string"!=typeof t.button_html&&(t.button_html="Display options..."),"string"!=typeof t.button_title&&(t.button_title="Control how plot items are displayed"),super(...arguments),this._event_name=t.custom_event_name||"widget_display_options_choice";const s=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],i=this.parent_panel.data_layers[t.layer_name];if(!i)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=i.layout,n={};s.forEach((t=>{const e=a[t];void 0!==e&&(n[t]=nt(e))})),this._selected_item="default",this.button=new ft(this).setColor(t.color).setHtml(t.button_html).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const t=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const e=this.button.menu.inner_selector.append("table"),a=this.layout,o=(a,o,r)=>{const l=e.append("tr"),h=`${t}${r}`;l.append("td").append("input").attr("id",h).attr("type","radio").attr("name",`display-option-${t}`).attr("value",r).style("margin",0).property("checked",r===this._selected_item).on("click",(()=>{s.forEach((t=>{const e=void 0!==o[t];i.layout[t]=e?o[t]:n[t]})),this.parent_svg.emit(this._event_name,{choice:a},!0),this._selected_item=r,this.parent_panel.render();const t=this.parent_panel.legend;t&&t.render()})),l.append("td").append("label").style("font-weight","normal").attr("for",h).text(a)},r=a.default_config_display_name||"Default style";return o(r,n,"default"),a.options.forEach(((t,e)=>o(t.display_name,t.display,e))),this}))}update(){return this.button.show(),this}}class Tt extends yt{constructor(t,e){if("string"!=typeof t.button_html&&(t.button_html="Set option..."),"string"!=typeof t.button_title&&(t.button_title="Choose an option to customize the plot"),super(t,e),this.parent_panel)throw new Error("This widget is designed to set global options, so it can only be used at the top (plot) level");if(!t.state_field)throw new Error("Must specify the `state_field` that this widget controls");if(this._event_name=t.custom_event_name||"widget_set_state_choice",this._selected_item=this.parent_plot.state[t.state_field]||t.options[0].value,!t.options.find((t=>t.value===this._selected_item)))throw new Error("There is an existing state value that does not match the known values in this widget");this.button=new ft(this).setColor(t.color).setHtml(t.button_html+(t.show_selected?this._selected_item:"")).setTitle(t.button_title).setOnclick((()=>{this.button.menu.populate()})),this.button.menu.setPopulate((()=>{const e=Math.floor(1e4*Math.random()).toString();this.button.menu.inner_selector.html("");const s=this.button.menu.inner_selector.append("table"),i=(i,a,n)=>{const o=s.append("tr"),r=`${e}${n}`;o.append("td").append("input").attr("id",r).attr("type","radio").attr("name",`set-state-${e}`).attr("value",n).style("margin",0).property("checked",a===this._selected_item).on("click",(()=>{const e={};e[t.state_field]=a,this._selected_item=a,this.parent_plot.applyState(e),this.button.setHtml(t.button_html+(t.show_selected?this._selected_item:"")),this.parent_svg.emit(this._event_name,{choice_name:i,choice_value:a,state_field:t.state_field},!0)})),o.append("td").append("label").style("font-weight","normal").attr("for",r).text(i)};return t.options.forEach(((t,e)=>i(t.display_name,t.value,e))),this}))}update(){return this.button.show(),this}}const Lt=new c;for(let[t,e]of Object.entries(a))Lt.add(t,e);const jt=Lt;class Pt{constructor(t){this.parent=t,this.id=`${this.parent.getBaseId()}.toolbar`,this.type=this.parent.parent?"panel":"plot",this.parent_plot=this.parent.parent_plot,this.selector=null,this.widgets=[],this.hide_timeout=null,this.persist=!1,this.initialize()}initialize(){const t=this.parent.layout.toolbar.widgets;return Array.isArray(t)&&t.forEach((t=>{this.addWidget(t)})),"panel"===this.type&&I.select(this.parent.parent.svg.node().parentNode).on(`mouseover.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.selector&&"hidden"!==this.selector.style("visibility")||this.show()})).on(`mouseout.${this.id}`,(()=>{clearTimeout(this.hide_timeout),this.hide_timeout=setTimeout((()=>{this.hide()}),300)})),this}addWidget(t){try{const e=jt.create(t.type,t,this);return this.widgets.push(e),e}catch(t){console.warn("Failed to create widget"),console.error(t)}}shouldPersist(){if(this.persist)return!0;let t=!1;return this.widgets.forEach((e=>{t=t||e.shouldPersist()})),t=t||this.parent_plot._panel_boundaries.dragging||this.parent_plot._interaction.dragging,!!t}show(){if(!this.selector){switch(this.type){case"plot":this.selector=I.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=I.select(this.parent.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain").classed("lz-panel-toolbar",!0);break;default:throw new Error(`Toolbar cannot be a child of ${this.type}`)}this.selector.classed("lz-toolbar",!0).classed(`lz-${this.type}-toolbar`,!0).attr("id",this.id)}return this.widgets.forEach((t=>t.show())),this.selector.style("visibility","visible"),this.update()}update(){return this.selector?(this.widgets.forEach((t=>t.update())),this.position()):this}position(){if(!this.selector)return this;if("panel"===this.type){const t=this.parent._getPageOrigin(),e=`${(t.y+3.5).toString()}px`,s=`${t.x.toString()}px`,i=`${(this.parent_plot.layout.width-4).toString()}px`;this.selector.style("position","absolute").style("top",e).style("left",s).style("width",i)}return this.widgets.forEach((t=>t.position())),this}hide(){return!this.selector||this.shouldPersist()||(this.widgets.forEach((t=>t.hide())),this.selector.style("visibility","hidden")),this}destroy(t){return void 0===t&&(t=!1),this.selector?(this.shouldPersist()&&!t||(this.widgets.forEach((t=>t.destroy(!0))),this.widgets=[],this.selector.remove(),this.selector=null),this):this}}const Rt={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:14,hidden:!1};class It{constructor(t){return this.parent=t,this.id=`${this.parent.getBaseId()}.legend`,this.parent.layout.legend=at(this.parent.layout.legend||{},Rt),this.layout=this.parent.layout.legend,this.selector=null,this.background_rect=null,this.elements=[],this.elements_group=null,this.hidden=!1,this.render()}render(){this.selector||(this.selector=this.parent.svg.group.append("g").attr("id",`${this.parent.getBaseId()}.legend`).attr("class","lz-legend")),this.background_rect||(this.background_rect=this.selector.append("rect").attr("width",100).attr("height",100).attr("class","lz-legend-background")),this.elements_group||(this.elements_group=this.selector.append("g")),this.elements.forEach((t=>t.remove())),this.elements=[];const t=+this.layout.padding||1;let e=t,s=t,i=0;this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((a=>{const n=this.parent.data_layers[a].layout.legend;Array.isArray(n)&&n.forEach((a=>{const n=this.elements_group.append("g").attr("transform",`translate(${e}, ${s})`),o=+a.label_size||+this.layout.label_size;let r=0,l=o/2+t/2;i=Math.max(i,o+t);const h=a.shape||"",c=ot(h);if("line"===h){const e=+a.length||16,s=o/4+t/2;n.append("path").attr("class",a.class||"").attr("d",`M0,${s}L${e},${s}`).call(gt,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,s=+a.height||e;n.append("rect").attr("class",a.class||"").attr("width",e).attr("height",s).attr("fill",a.color||{}).call(gt,a.style||{}),r=e+t,i=Math.max(i,s+t)}else if("ribbon"===h){const e=+a.width||25,s=+a.height||e,i="horizontal"===(a.orientation||"vertical");let o=a.color_stops;const h=n.append("g"),c=h.append("g"),d=h.append("g");let u=0;if(a.tick_labels){let t;t=i?[0,e*o.length-1]:[s*o.length-1,0];const n=I.scaleLinear().domain(I.extent(a.tick_labels)).range(t),r=(i?I.axisTop:I.axisRight)(n).tickSize(3).tickValues(a.tick_labels).tickFormat((t=>t));d.call(r).attr("class","lz-axis"),u=d.node().getBoundingClientRect().height}i?(d.attr("transform",`translate(0, ${u})`),c.attr("transform",`translate(0, ${u})`)):(h.attr("transform","translate(5, 0)"),d.attr("transform",`translate(${e}, 0)`)),i||(o=o.slice(),o.reverse());for(let t=0;tt&&a>this.parent.parent.layout.width&&(s+=i,e=t,n.attr("transform",`translate(${e}, ${s})`)),e+=d.width+3*t}this.elements.push(n)}))}));const a=this.elements_group.node().getBoundingClientRect();return this.layout.width=a.width+2*this.layout.padding,this.layout.height=a.height+2*this.layout.padding,this.background_rect.attr("width",this.layout.width).attr("height",this.layout.height),this.selector.style("visibility",this.layout.hidden?"hidden":"visible"),this.position()}position(){if(!this.selector)return this;const t=this.selector.node().getBoundingClientRect();isNaN(+this.layout.pad_from_bottom)||(this.layout.origin.y=this.parent.layout.height-t.height-+this.layout.pad_from_bottom),isNaN(+this.layout.pad_from_right)||(this.layout.origin.x=this.parent.parent.layout.width-t.width-+this.layout.pad_from_right),this.selector.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`)}hide(){this.layout.hidden=!0,this.render()}show(){this.layout.hidden=!1,this.render()}}const Ct={id:"",tag:"custom_data_type",title:{text:"",style:{},x:10,y:22},y_index:null,min_height:1,height:1,origin:{x:0,y:null},margin:{top:0,right:0,bottom:0,left:0},background_click:"clear_selections",toolbar:{widgets:[]},cliparea:{height:0,width:0,origin:{x:0,y:0}},axes:{x:{},y1:{},y2:{}},legend:null,interaction:{drag_background_to_pan:!1,drag_x_ticks_to_scale:!1,drag_y1_ticks_to_scale:!1,drag_y2_ticks_to_scale:!1,scroll_to_zoom:!1,x_linked:!1,y1_linked:!1,y2_linked:!1},show_loading_indicator:!0,data_layers:[]};class Dt{constructor(t,e){if("object"!=typeof t)throw new Error("Unable to create panel, invalid layout");if(this.parent=e||null,this.parent_plot=e,"string"!=typeof t.id||!t.id)throw new Error('Panel layouts must specify "id"');if(this.parent&&void 0!==this.parent.panels[t.id])throw new Error(`Cannot create panel with id [${t.id}]; panel with that id already exists`);this.id=t.id,this._initialized=!1,this._layout_idx=null,this.svg={},this.layout=at(t||{},Ct),this.parent?(this.state=this.parent.state,this._state_id=this.id,this.state[this._state_id]=this.state[this._state_id]||{}):(this.state=null,this._state_id=null),this.data_layers={},this._data_layer_ids_by_z_index=[],this._data_promises=[],this.x_scale=null,this.y1_scale=null,this.y2_scale=null,this.x_extent=null,this.y1_extent=null,this.y2_extent=null,this.x_ticks=[],this.y1_ticks=[],this.y2_ticks=[],this._zoom_timeout=null,this._event_hooks={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e,s){if(s=s||!1,"string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);"boolean"==typeof e&&2===arguments.length&&(s=e,e=null);const i={sourceID:this.getBaseId(),target:this,data:e||null};return this._event_hooks[t]&&this._event_hooks[t].forEach((t=>{t.call(this,i)})),s&&this.parent&&this.parent.emit(t,i),this}setTitle(t){if("string"==typeof this.layout.title){const t=this.layout.title;this.layout.title={text:t,x:0,y:0,style:{}}}return"string"==typeof t?this.layout.title.text=t:"object"==typeof t&&null!==t&&(this.layout.title=at(t,this.layout.title)),this.layout.title.text.length?this.title.attr("display",null).attr("x",parseFloat(this.layout.title.x)).attr("y",parseFloat(this.layout.title.y)).text(this.layout.title.text).call(gt,this.layout.title.style):this.title.attr("display","none"),this}addDataLayer(t){if("object"!=typeof t||"string"!=typeof t.id||!t.id.length)throw new Error("Invalid data layer layout");if(void 0!==this.data_layers[t.id])throw new Error(`Cannot create data_layer with id '${t.id}'; data layer with that id already exists in the panel`);if("string"!=typeof t.type)throw new Error("Invalid data layer type");"object"!=typeof t.y_axis||void 0!==t.y_axis.axis&&[1,2].includes(t.y_axis.axis)||(t.y_axis.axis=1);const e=xe.create(t.type,t,this);if(this.data_layers[e.id]=e,null!==e.layout.z_index&&!isNaN(e.layout.z_index)&&this._data_layer_ids_by_z_index.length>0)e.layout.z_index<0&&(e.layout.z_index=Math.max(this._data_layer_ids_by_z_index.length+e.layout.z_index,0)),this._data_layer_ids_by_z_index.splice(e.layout.z_index,0,e.id),this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}));else{const t=this._data_layer_ids_by_z_index.push(e.id);this.data_layers[e.id].layout.z_index=t-1}let s=null;return this.layout.data_layers.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id]._layout_idx=s,this.data_layers[e.id]}removeDataLayer(t){const e=this.data_layers[t];if(!e)throw new Error(`Unable to remove data layer, ID not found: ${t}`);return e.destroyAllTooltips(),e.svg.container&&e.svg.container.remove(),this.layout.data_layers.splice(e._layout_idx,1),delete this.state[e._state_id],delete this.data_layers[t],this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(t),1),this.applyDataLayerZIndexesToDataLayerLayouts(),this.layout.data_layers.forEach(((t,e)=>{this.data_layers[t.id]._layout_idx=e})),this}clearSelections(){return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].setAllElementStatus("selected",!1)})),this}render(){this.svg.container.attr("transform",`translate(${this.layout.origin.x}, ${this.layout.origin.y})`),this.svg.clipRect.attr("width",this.parent_plot.layout.width).attr("height",this.layout.height);const{cliparea:t}=this.layout,{margin:e}=this.layout;this.inner_border.attr("x",e.left).attr("y",e.top).attr("width",this.parent_plot.layout.width-(e.left+e.right)).attr("height",this.layout.height-(e.top+e.bottom)),this.layout.inner_border&&this.inner_border.style("stroke-width",1).style("stroke",this.layout.inner_border),this.setTitle(),this.generateExtents();const s=function(t,e){const s=Math.pow(-10,e),i=Math.pow(-10,-e),a=Math.pow(10,-e),n=Math.pow(10,e);return t===1/0&&(t=n),t===-1/0&&(t=s),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,n),a)),t<0&&(t=Math.max(Math.min(t,i),s)),t},i={},a=this.layout.axes;if(this.x_extent){const t={start:0,end:this.layout.cliparea.width};a.x.range&&(t.start=a.x.range.start||t.start,t.end=a.x.range.end||t.end),i.x=[t.start,t.end],i.x_shifted=[t.start,t.end]}if(this.y1_extent){const e={start:t.height,end:0};a.y1.range&&(e.start=a.y1.range.start||e.start,e.end=a.y1.range.end||e.end),i.y1=[e.start,e.end],i.y1_shifted=[e.start,e.end]}if(this.y2_extent){const e={start:t.height,end:0};a.y2.range&&(e.start=a.y2.range.start||e.start,e.end=a.y2.range.end||e.end),i.y2=[e.start,e.end],i.y2_shifted=[e.start,e.end]}let{_interaction:n}=this.parent;const o=n.dragging;if(n.panel_id&&(n.panel_id===this.id||n.linked_panel_ids.includes(this.id))){let a,r=null;if(n.zooming&&"function"==typeof this.x_scale){const s=Math.abs(this.x_extent[1]-this.x_extent[0]),o=Math.round(this.x_scale.invert(i.x_shifted[1]))-Math.round(this.x_scale.invert(i.x_shifted[0]));let r=n.zooming.scale;const l=Math.floor(o*(1/r));r<1&&!isNaN(this.parent.layout.max_region_scale)?r=1/(Math.min(l,this.parent.layout.max_region_scale)/o):r>1&&!isNaN(this.parent.layout.min_region_scale)&&(r=1/(Math.max(l,this.parent.layout.min_region_scale)/o));const h=Math.floor(s*r);a=n.zooming.center-e.left-this.layout.origin.x;const c=a/t.width,d=Math.max(Math.floor(this.x_scale.invert(i.x_shifted[0])-(h-o)*c),1);i.x_shifted=[this.x_scale(d),this.x_scale(d+h)]}else if(o)switch(o.method){case"background":i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x;break;case"x_tick":I.event&&I.event.shiftKey?(i.x_shifted[0]=+o.dragged_x,i.x_shifted[1]=t.width+o.dragged_x):(a=o.start_x-e.left-this.layout.origin.x,r=s(a/(a+o.dragged_x),3),i.x_shifted[0]=0,i.x_shifted[1]=Math.max(t.width*(1/r),1));break;case"y1_tick":case"y2_tick":{const n=`y${o.method[1]}_shifted`;I.event&&I.event.shiftKey?(i[n][0]=t.height+o.dragged_y,i[n][1]=+o.dragged_y):(a=t.height-(o.start_y-e.top-this.layout.origin.y),r=s(a/(a-o.dragged_y),3),i[n][0]=t.height,i[n][1]=t.height-t.height*(1/r))}}}if(["x","y1","y2"].forEach((t=>{this[`${t}_extent`]&&(this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[`${t}_shifted`]),this[`${t}_extent`]=[this[`${t}_scale`].invert(i[t][0]),this[`${t}_scale`].invert(i[t][1])],this[`${t}_scale`]=I.scaleLinear().domain(this[`${t}_extent`]).range(i[t]),this.renderAxis(t))})),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!I.event.shiftKey&&!I.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(I.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=I.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,I.event.wheelDelta||-I.event.detail||-I.event.deltaY));0!==e&&(this.parent._interaction={panel_id:this.id,linked_panel_ids:this.getLinkedPanelIds("x"),zooming:{scale:e<1?.9:1.1,center:t[0]}},this.render(),n=this.parent._interaction,n.linked_panel_ids.forEach((t=>{this.parent.panels[t].render()})),null!==this._zoom_timeout&&clearTimeout(this._zoom_timeout),this._zoom_timeout=setTimeout((()=>{this.parent._interaction={},this.parent.applyState({start:this.x_extent[0],end:this.x_extent[1]})}),500))};this.svg.container.on("wheel.zoom",t).on("mousewheel.zoom",t).on("DOMMouseScroll.zoom",t)}return this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].draw().render()})),this.legend&&this.legend.render(),this}addBasicLoader(t=!0){return this.layout.show_loading_indicator&&this._initialized||(t&&this.loader.show("Loading...").animate(),this.on("data_requested",(()=>{this.loader.show("Loading...").animate()})),this.on("data_rendered",(()=>{this.loader.hide()})),this.layout.show_loading_indicator=!0),this}applyDataLayerZIndexesToDataLayerLayouts(){this._data_layer_ids_by_z_index.forEach(((t,e)=>{this.data_layers[t].layout.z_index=e}))}getBaseId(){return`${this.parent.id}.${this.id}`}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.layout.origin.x,y:t.y+this.layout.origin.y}}initializeLayout(){return this.setDimensions(),this.setOrigin(),this.setMargin(),this.x_range=[0,this.layout.cliparea.width],this.y1_range=[this.layout.cliparea.height,0],this.y2_range=[this.layout.cliparea.height,0],["x","y1","y2"].forEach((t=>{const e=this.layout.axes[t];Object.keys(e).length&&!1!==e.render?(e.render=!0,e.label=e.label||null):e.render=!1})),this.layout.data_layers.forEach((t=>{this.addDataLayer(t)})),this}setDimensions(t,e){const s=this.layout;return void 0!==t&&void 0!==e&&!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0&&(this.parent.layout.width=Math.round(+t),s.height=Math.max(Math.round(+e),s.min_height)),s.cliparea.width=Math.max(this.parent_plot.layout.width-(s.margin.left+s.margin.right),0),s.cliparea.height=Math.max(s.height-(s.margin.top+s.margin.bottom),0),this.svg.clipRect&&this.svg.clipRect.attr("width",this.parent.layout.width).attr("height",s.height),this._initialized&&(this.render(),this.curtain.update(),this.loader.update(),this.toolbar.update(),this.legend&&this.legend.position()),this}setOrigin(t,e){return!isNaN(t)&&t>=0&&(this.layout.origin.x=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(this.layout.origin.y=Math.max(Math.round(+e),0)),this._initialized&&this.render(),this}setMargin(t,e,s,i){let a;const{cliparea:n,margin:o}=this.layout;return!isNaN(t)&&t>=0&&(o.top=Math.max(Math.round(+t),0)),!isNaN(e)&&e>=0&&(o.right=Math.max(Math.round(+e),0)),!isNaN(s)&&s>=0&&(o.bottom=Math.max(Math.round(+s),0)),!isNaN(i)&&i>=0&&(o.left=Math.max(Math.round(+i),0)),o.top+o.bottom>this.layout.height&&(a=Math.floor((o.top+o.bottom-this.layout.height)/2),o.top-=a,o.bottom-=a),o.left+o.right>this.parent_plot.layout.width&&(a=Math.floor((o.left+o.right-this.parent_plot.layout.width)/2),o.left-=a,o.right-=a),["top","right","bottom","left"].forEach((t=>{o[t]=Math.max(o[t],0)})),n.width=Math.max(this.parent_plot.layout.width-(o.left+o.right),0),n.height=Math.max(this.layout.height-(o.top+o.bottom),0),n.origin.x=o.left,n.origin.y=o.top,this._initialized&&this.render(),this}initialize(){const t=this.getBaseId();this.svg.container=this.parent.svg.append("g").attr("id",`${t}.panel_container`).attr("transform",`translate(${this.layout.origin.x||0}, ${this.layout.origin.y||0})`);const e=this.svg.container.append("clipPath").attr("id",`${t}.clip`);if(this.svg.clipRect=e.append("rect").attr("width",this.parent_plot.layout.width).attr("height",this.layout.height),this.svg.group=this.svg.container.append("g").attr("id",`${t}.panel`).attr("clip-path",`url(#${t}.clip)`),this.curtain=_t.call(this),this.loader=pt.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new Pt(this),this.inner_border=this.svg.group.append("rect").attr("class","lz-panel-background").on("click",(()=>{"clear_selections"===this.layout.background_click&&this.clearSelections()})),this.title=this.svg.group.append("text").attr("class","lz-panel-title"),void 0!==this.layout.title&&this.setTitle(),this.svg.x_axis=this.svg.group.append("g").attr("id",`${t}.x_axis`).attr("class","lz-x lz-axis"),this.layout.axes.x.render&&(this.svg.x_axis_label=this.svg.x_axis.append("text").attr("class","lz-x lz-axis lz-label").attr("text-anchor","middle")),this.svg.y1_axis=this.svg.group.append("g").attr("id",`${t}.y1_axis`).attr("class","lz-y lz-y1 lz-axis"),this.layout.axes.y1.render&&(this.svg.y1_axis_label=this.svg.y1_axis.append("text").attr("class","lz-y1 lz-axis lz-label").attr("text-anchor","middle")),this.svg.y2_axis=this.svg.group.append("g").attr("id",`${t}.y2_axis`).attr("class","lz-y lz-y2 lz-axis"),this.layout.axes.y2.render&&(this.svg.y2_axis_label=this.svg.y2_axis.append("text").attr("class","lz-y2 lz-axis lz-label").attr("text-anchor","middle")),this._data_layer_ids_by_z_index.forEach((t=>{this.data_layers[t].initialize()})),this.legend=null,this.layout.legend&&(this.legend=new It(this)),this.layout.interaction.drag_background_to_pan){const t=`.${this.parent.id}.${this.id}.interaction.drag`,e=()=>this.parent.startDrag(this,"background");this.svg.container.select(".lz-panel-background").on(`mousedown${t}.background`,e).on(`touchstart${t}.background`,e)}return this}resortDataLayers(){const t=[];this._data_layer_ids_by_z_index.forEach((e=>{t.push(this.data_layers[e].layout.z_index)})),this.svg.group.selectAll("g.lz-data_layer-container").data(t).sort(I.ascending),this.applyDataLayerZIndexesToDataLayerLayouts()}getLinkedPanelIds(t){const e=[];return["x","y1","y2"].includes(t=t||null)&&this.layout.interaction[`${t}_linked`]?(this.parent._panel_ids_by_y_index.forEach((s=>{s!==this.id&&this.parent.panels[s].layout.interaction[`${t}_linked`]&&e.push(s)})),e):e}moveUp(){const{parent:t}=this,e=this.layout.y_index;return t._panel_ids_by_y_index[e-1]&&(t._panel_ids_by_y_index[e]=t._panel_ids_by_y_index[e-1],t._panel_ids_by_y_index[e-1]=this.id,t.applyPanelYIndexesToPanelLayouts(),t.positionPanels()),this}moveDown(){const{_panel_ids_by_y_index:t}=this.parent;return t[this.layout.y_index+1]&&(t[this.layout.y_index]=t[this.layout.y_index+1],t[this.layout.y_index+1]=this.id,this.parent.applyPanelYIndexesToPanelLayouts(),this.parent.positionPanels()),this}reMap(){this.emit("data_requested"),this._data_promises=[],this.curtain.hide();for(let t in this.data_layers)try{this._data_promises.push(this.data_layers[t].reMap())}catch(t){console.error(t),this.curtain.show(t.message||t)}return Promise.all(this._data_promises).then((()=>{this._initialized=!0,this.render(),this.emit("layout_changed",!0),this.emit("data_rendered")})).catch((t=>{console.error(t),this.curtain.show(t.message||t)}))}generateExtents(){["x","y1","y2"].forEach((t=>{this[`${t}_extent`]=null}));for(let t in this.data_layers){const e=this.data_layers[t];if(e.layout.x_axis&&!e.layout.x_axis.decoupled&&(this.x_extent=I.extent((this.x_extent||[]).concat(e.getAxisExtent("x")))),e.layout.y_axis&&!e.layout.y_axis.decoupled){const t=`y${e.layout.y_axis.axis}`;this[`${t}_extent`]=I.extent((this[`${t}_extent`]||[]).concat(e.getAxisExtent("y")))}}return this.layout.axes.x&&"state"===this.layout.axes.x.extent&&(this.x_extent=[this.state.start,this.state.end]),this}generateTicks(t){if(this.layout.axes[t].ticks){const e=this.layout.axes[t].ticks;if(Array.isArray(e))return e;if("object"==typeof e){const s=this,i={position:e.position};return this._data_layer_ids_by_z_index.reduce(((e,a)=>{const n=s.data_layers[a];return e.concat(n.getTicks(t,i))}),[]).map((t=>{let s={};return s=at(s,e),at(s,t)}))}}return this[`${t}_extent`]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=.75,n=1.5,o=.5+1.5*n,r=Math.abs(t[0]-t[1]);let l=r/s;Math.log(r)/Math.LN10<-2&&(l=Math.max(Math.abs(r))*a/i);const h=Math.pow(10,Math.floor(Math.log(l)/Math.LN10));let c=0;h<1&&0!==h&&(c=Math.abs(Math.round(Math.log(h)/Math.LN10)));let d=h;2*h-l0&&(_=parseFloat(_.toFixed(c)));u.push(_),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||u[0]t[1]&&u.pop();return u}(this[`${t}_extent`],"both"):[]}renderAxis(t){if(!["x","y1","y2"].includes(t))throw new Error(`Unable to render axis; invalid axis identifier: ${t}`);const e=this.layout.axes[t].render&&"function"==typeof this[`${t}_scale`]&&!isNaN(this[`${t}_scale`](0));if(this[`${t}_axis`]&&this.svg.container.select(`g.lz-axis.lz-${t}`).style("display",e?null:"none"),!e)return this;const s={x:{position:`translate(${this.layout.margin.left}, ${this.layout.height-this.layout.margin.bottom})`,orientation:"bottom",label_x:this.layout.cliparea.width/2,label_y:this.layout.axes[t].label_offset||0,label_rotate:null},y1:{position:`translate(${this.layout.margin.left}, ${this.layout.margin.top})`,orientation:"left",label_x:-1*(this.layout.axes[t].label_offset||0),label_y:this.layout.cliparea.height/2,label_rotate:-90},y2:{position:`translate(${this.parent_plot.layout.width-this.layout.margin.right}, ${this.layout.margin.top})`,orientation:"right",label_x:this.layout.axes[t].label_offset||0,label_y:this.layout.cliparea.height/2,label_rotate:-90}};this[`${t}_ticks`]=this.generateTicks(t);const i=(t=>{for(let e=0;eqt(t,6)));else{let e=this[`${t}_ticks`].map((e=>e[t.substr(0,1)]));this[`${t}_axis`].tickValues(e).tickFormat(((e,s)=>this[`${t}_ticks`][s].text))}if(this.svg[`${t}_axis`].attr("transform",s[t].position).call(this[`${t}_axis`]),!i){const e=I.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=I.select(this).select("text");s[`${t}_ticks`][i].style&>(a,s[`${t}_ticks`][i].style),s[`${t}_ticks`][i].transform&&a.attr("transform",s[`${t}_ticks`][i].transform)}))}const n=this.layout.axes[t].label||null;return null!==n&&(this.svg[`${t}_axis_label`].attr("x",s[t].label_x).attr("y",s[t].label_y).text(Ht(n,this.state)).attr("fill","currentColor"),null!==s[t].label_rotate&&this.svg[`${t}_axis_label`].attr("transform",`rotate(${s[t].label_rotate} ${s[t].label_x}, ${s[t].label_y})`)),["x","y1","y2"].forEach((t=>{if(this.layout.interaction[`drag_${t}_ticks_to_scale`]){const e=`.${this.parent.id}.${this.id}.interaction.drag`,s=function(){"function"==typeof I.select(this).node().focus&&I.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";I.event&&I.event.shiftKey&&(i="move"),I.select(this).style("font-weight","bold").style("cursor",i).on(`keydown${e}`,s).on(`keyup${e}`,s)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on(`mouseover${e}`,s).on(`mouseout${e}`,(function(){I.select(this).style("font-weight","normal").on(`keydown${e}`,null).on(`keyup${e}`,null)})).on(`mousedown${e}`,(()=>{this.parent.startDrag(this,`${t}_tick`)}))}})),this}scaleHeightToData(t){null===(t=+t||null)&&this._data_layer_ids_by_z_index.forEach((e=>{const s=this.data_layers[e].getAbsoluteDataHeight();+s&&(t=null===t?+s:Math.max(t,+s))})),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.parent_plot.layout.width,t),this.parent.setDimensions(),this.parent.positionPanels())}setAllElementStatus(t,e){this._data_layer_ids_by_z_index.forEach((s=>{this.data_layers[s].setAllElementStatus(t,e)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;Dt.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},Dt.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const Bt={state:{},width:800,min_width:400,min_region_scale:null,max_region_scale:null,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class Ut{constructor(t,e,s){this._initialized=!1,this.parent_plot=this,this.id=t,this.container=null,this.svg=null,this.panels={},this._panel_ids_by_y_index=[],this._remap_promises=[],this.layout=s,at(this.layout,Bt),this._base_layout=nt(this.layout),this.state=this.layout.state,this.lzd=new ut(e),this._external_listeners=new Map,this._event_hooks={},this._interaction={},this.initializeLayout()}on(t,e){if("string"!=typeof t)throw new Error(`Unable to register event hook. Event name must be a string: ${t.toString()}`);if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this._event_hooks[t]||(this._event_hooks[t]=[]),this._event_hooks[t].push(e),e}off(t,e){const s=this._event_hooks[t];if("string"!=typeof t||!Array.isArray(s))throw new Error(`Unable to remove event hook, invalid event: ${t.toString()}`);if(void 0===e)this._event_hooks[t]=[];else{const t=s.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");s.splice(t,1)}return this}emit(t,e){const s=this._event_hooks[t];if("string"!=typeof t)throw new Error(`LocusZoom attempted to throw an invalid event: ${t.toString()}`);if(!s&&!this._event_hooks.any_lz_event)return this;const i=this.getBaseId();let a;if(a=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},s&&s.forEach((t=>{t.call(this,a)})),"any_lz_event"!==t){const e=Object.assign({event_name:t},a);this.emit("any_lz_event",e)}return this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Dt(t,this);if(this.panels[e.id]=e,null!==e.layout.y_index&&!isNaN(e.layout.y_index)&&this._panel_ids_by_y_index.length>0)e.layout.y_index<0&&(e.layout.y_index=Math.max(this._panel_ids_by_y_index.length+e.layout.y_index,0)),this._panel_ids_by_y_index.splice(e.layout.y_index,0,e.id),this.applyPanelYIndexesToPanelLayouts();else{const t=this._panel_ids_by_y_index.push(e.id);this.panels[e.id].layout.y_index=t-1}let s=null;return this.layout.panels.forEach(((t,i)=>{t.id===e.id&&(s=i)})),null===s&&(s=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id]._layout_idx=s,this._initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this._total_height)),this.panels[e.id]}clearPanelData(t,e){let s;return e=e||"wipe",s=t?[t]:Object.keys(this.panels),s.forEach((t=>{this.panels[t]._data_layer_ids_by_z_index.forEach((s=>{const i=this.panels[t].data_layers[s];i.destroyAllTooltips(),delete i._layer_state,delete this.layout.state[i._state_id],"reset"===e&&i._setDefaultState()}))})),this}removePanel(t){const e=this.panels[t];if(!e)throw new Error(`Unable to remove panel, ID not found: ${t}`);return this._panel_boundaries.hide(),this.clearPanelData(t),e.loader.hide(),e.toolbar.destroy(!0),e.curtain.hide(),e.svg.container&&e.svg.container.remove(),this.layout.panels.splice(e._layout_idx,1),delete this.panels[t],delete this.layout.state[t],this.layout.panels.forEach(((t,e)=>{this.panels[t.id]._layout_idx=e})),this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(t),1),this.applyPanelYIndexesToPanelLayouts(),this._initialized&&(this.positionPanels(),this.setDimensions(this.layout.width,this._total_height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e){const{from_layer:s,namespace:i,data_operations:a,onerror:n}=t,o=n||function(t){console.error("An error occurred while acting on an external callback",t)};if(s){const t=`${this.getBaseId()}.`,i=s.startsWith(t)?s:`${t}${s}`;let a=!1;for(let t of Object.values(this.panels))if(a=Object.values(t.data_layers).some((t=>t.getBaseId()===i)),a)break;if(!a)throw new Error(`Could not subscribe to unknown data layer ${i}`);const n=t=>{if(t.data.layer===i)try{e(t.data.content,this)}catch(t){o(t)}};return this.on("data_from_layer",n),n}if(!i)throw new Error("subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option");const[r,l]=this.lzd.config_to_sources(i,a),h=()=>{try{this.lzd.getData(this.state,r,l).then((t=>e(t,this))).catch(o)}catch(t){o(t)}};return this.on("data_rendered",h),h}applyState(t){if("object"!=typeof(t=t||{}))throw new Error(`applyState only accepts an object; ${typeof t} given`);let e={chr:this.state.chr,start:this.state.start,end:this.state.end};for(let s in t)e[s]=t[s];e=function(t,e){e=e||{};let s,i=!1,a=null;if(void 0!==(t=t||{}).chr&&void 0!==t.start&&void 0!==t.end){if(t.start=Math.max(parseInt(t.start),1),t.end=Math.max(parseInt(t.end),1),isNaN(t.start)&&isNaN(t.end))t.start=1,t.end=1,a=.5,s=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,s=0,t.start=isNaN(t.start)?t.end:t.start,t.end=isNaN(t.end)?t.start:t.end;else{if(a=Math.round((t.start+t.end)/2),s=t.end-t.start,s<0){const e=t.start;t.end=t.start,t.start=e,s=t.end-t.start}a<0&&(t.start=1,t.end=1,s=0)}i=!0}return e.min_region_scale&&i&&se.max_region_scale&&(t.start=Math.max(a-Math.floor(e.max_region_scale/2),1),t.end=t.start+e.max_region_scale),t}(e,this.layout);for(let t in e)this.state[t]=e[t];this.emit("data_requested"),this._remap_promises=[],this.loading_data=!0;for(let t in this.panels)this._remap_promises.push(this.panels[t].reMap());return Promise.all(this._remap_promises).catch((t=>{console.error(t),this.curtain.show(t.message||t),this.loading_data=!1})).then((()=>{this.toolbar.update(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.toolbar.update(),e._data_layer_ids_by_z_index.forEach((t=>{e.data_layers[t].applyAllElementStatus()}))})),this.emit("layout_changed"),this.emit("data_rendered"),this.emit("state_changed",t);const{chr:e,start:s,end:i}=this.state;Object.keys(t).some((t=>["chr","start","end"].includes(t)))&&this.emit("region_changed",{chr:e,start:s,end:i}),this.loading_data=!1}))}trackExternalListener(t,e,s){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const i=this._external_listeners.get(t),a=i.get(e)||[];a.includes(s)||a.push(s),i.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[s,i]of e)for(let e of i)t.removeEventListener(s,e);const t=this.svg.node().parentNode;if(!t)throw new Error("Plot has already been removed");for(;t.lastElementChild;)t.removeChild(t.lastElementChild);t.outerHTML=t.outerHTML,this._initialized=!1,this.svg=null,this.panels=null}mutateLayout(){Object.values(this.panels).forEach((t=>{Object.values(t.data_layers).forEach((t=>t.mutateLayout()))}))}_canInteract(t){t=t||null;const{_interaction:e}=this;return t?(void 0===e.panel_id||e.panel_id===t)&&!this.loading_data:!(e.dragging||e.zooming||this.loading_data)}_getPageOrigin(){const t=this.svg.node().getBoundingClientRect();let e=document.documentElement.scrollLeft||document.body.scrollLeft,s=document.documentElement.scrollTop||document.body.scrollTop,i=this.svg.node();for(;null!==i.parentNode;)if(i=i.parentNode,i!==document&&"static"!==I.select(i).style("position")){e=-1*i.getBoundingClientRect().left,s=-1*i.getBoundingClientRect().top;break}return{x:e+t.left,y:s+t.top,width:t.width,height:t.height}}getContainerOffset(){const t={top:0,left:0};let e=this.container.offsetParent||null;for(;null!==e;)t.top+=e.offsetTop,t.left+=e.offsetLeft,e=e.offsetParent||null;return t}applyPanelYIndexesToPanelLayouts(){this._panel_ids_by_y_index.forEach(((t,e)=>{this.panels[t].layout.y_index=e}))}getBaseId(){return this.id}rescaleSVG(){const t=this.svg.node().getBoundingClientRect();return this.setDimensions(t.width,t.height),this}initializeLayout(){if(isNaN(this.layout.width)||this.layout.width<=0)throw new Error("Plot layout parameter `width` must be a positive number");return this.layout.responsive_resize=!!this.layout.responsive_resize,this.layout.panels.forEach((t=>{this.addPanel(t)})),this}setDimensions(t,e){if(!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){const s=e/this._total_height;this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.responsive_resize&&this.svg&&(this.layout.width=Math.max(this.svg.node().parentNode.getBoundingClientRect().width,this.layout.min_width));let i=0;this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t],a=this.layout.width,n=e.layout.height*s;e.setDimensions(a,n),e.setOrigin(0,i),i+=n,e.toolbar.update()}))}const s=this._total_height;return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${s}`),this.svg.attr("width",this.layout.width).attr("height",s)),this._initialized&&(this._panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){const t={left:0,right:0};for(let e of Object.values(this.panels))e.layout.interaction.x_linked&&(t.left=Math.max(t.left,e.layout.margin.left),t.right=Math.max(t.right,e.layout.margin.right));let e=0;return this._panel_ids_by_y_index.forEach((s=>{const i=this.panels[s],a=i.layout;if(i.setOrigin(0,e),e+=this.panels[s].layout.height,a.interaction.x_linked){const e=Math.max(t.left-a.margin.left,0)+Math.max(t.right-a.margin.right,0);a.width+=e,a.margin.left=t.left,a.margin.right=t.right,a.cliparea.origin.x=t.left}})),this.setDimensions(),this._panel_ids_by_y_index.forEach((t=>{const e=this.panels[t];e.setDimensions(this.layout.width,e.layout.height)})),this}initialize(){if(this.layout.responsive_resize){I.select(this.container).classed("lz-container-responsive",!0);const t=()=>window.requestAnimationFrame((()=>this.rescaleSVG()));if(window.addEventListener("resize",t),this.trackExternalListener(window,"resize",t),"undefined"!=typeof IntersectionObserver){const t={root:document.documentElement,threshold:.9};new IntersectionObserver(((t,e)=>{t.some((t=>t.intersectionRatio>0))&&this.rescaleSVG()}),t).observe(this.container)}const e=()=>this.setDimensions();window.addEventListener("load",e),this.trackExternalListener(window,"load",e)}if(this.layout.mouse_guide){const t=this.svg.append("g").attr("class","lz-mouse_guide").attr("id",`${this.id}.mouse_guide`),e=t.append("rect").attr("class","lz-mouse_guide-vertical").attr("x",-1),s=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this._mouse_guide={svg:t,vertical:e,horizontal:s}}this.curtain=_t.call(this),this.loader=pt.call(this),this._panel_boundaries={parent:this,hide_timeout:null,showing:!1,dragging:!1,selectors:[],corner_selector:null,show:function(){if(!this.showing&&!this.parent.curtain.showing){this.showing=!0,this.parent._panel_ids_by_y_index.forEach(((t,e)=>{const s=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");s.append("span");const i=I.drag();i.on("start",(()=>{this.dragging=!0})),i.on("end",(()=>{this.dragging=!1})),i.on("drag",(()=>{const t=this.parent.panels[this.parent._panel_ids_by_y_index[e]],s=t.layout.height;t.setDimensions(this.parent.layout.width,t.layout.height+I.event.dy);const i=t.layout.height-s;this.parent._panel_ids_by_y_index.forEach(((t,s)=>{const a=this.parent.panels[this.parent._panel_ids_by_y_index[s]];s>e&&(a.setOrigin(a.layout.origin.x,a.layout.origin.y+i),a.toolbar.position())})),this.parent.positionPanels(),this.position()})),s.call(i),this.parent._panel_boundaries.selectors.push(s)}));const t=I.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-corner-boundary").attr("title","Resize plot");t.append("span").attr("class","lz-panel-corner-boundary-outer"),t.append("span").attr("class","lz-panel-corner-boundary-inner");const e=I.drag();e.on("start",(()=>{this.dragging=!0})),e.on("end",(()=>{this.dragging=!1})),e.on("drag",(()=>{this.parent.setDimensions(this.parent.layout.width+I.event.dx,this.parent._total_height+I.event.dy)})),t.call(e),this.parent._panel_boundaries.corner_selector=t}return this.position()},position:function(){if(!this.showing)return this;const t=this.parent._getPageOrigin();this.selectors.forEach(((e,s)=>{const i=this.parent.panels[this.parent._panel_ids_by_y_index[s]],a=i._getPageOrigin(),n=t.x,o=a.y+i.layout.height-12,r=this.parent.layout.width-1;e.style("top",`${o}px`).style("left",`${n}px`).style("width",`${r}px`),e.select("span").style("width",`${r}px`)}));return this.corner_selector.style("top",t.y+this.parent._total_height-10-16+"px").style("left",t.x+this.parent.layout.width-10-16+"px"),this},hide:function(){return this.showing?(this.showing=!1,this.selectors.forEach((t=>{t.remove()})),this.selectors=[],this.corner_selector.remove(),this.corner_selector=null,this):this}},this.layout.panel_boundaries&&I.select(this.svg.node().parentNode).on(`mouseover.${this.id}.panel_boundaries`,(()=>{clearTimeout(this._panel_boundaries.hide_timeout),this._panel_boundaries.show()})).on(`mouseout.${this.id}.panel_boundaries`,(()=>{this._panel_boundaries.hide_timeout=setTimeout((()=>{this._panel_boundaries.hide()}),300)})),this.toolbar=new Pt(this).show();for(let t in this.panels)this.panels[t].initialize();const t=`.${this.id}`;if(this.layout.mouse_guide){const e=()=>{this._mouse_guide.vertical.attr("x",-1),this._mouse_guide.horizontal.attr("y",-1)},s=()=>{const t=I.mouse(this.svg.node());this._mouse_guide.vertical.attr("x",t[0]),this._mouse_guide.horizontal.attr("y",t[1])};this.svg.on(`mouseout${t}-mouse_guide`,e).on(`touchleave${t}-mouse_guide`,e).on(`mousemove${t}-mouse_guide`,s)}const e=()=>{this.stopDrag()},s=()=>{const{_interaction:t}=this;if(t.dragging){const e=I.mouse(this.svg.node());I.event&&I.event.preventDefault(),t.dragging.dragged_x=e[0]-t.dragging.start_x,t.dragging.dragged_y=e[1]-t.dragging.start_y,this.panels[t.panel_id].render(),t.linked_panel_ids.forEach((t=>{this.panels[t].render()}))}};this.svg.on(`mouseup${t}`,e).on(`touchend${t}`,e).on(`mousemove${t}`,s).on(`touchmove${t}`,s);const i=I.select("body").node();i&&(i.addEventListener("mouseup",e),i.addEventListener("touchend",e),this.trackExternalListener(i,"mouseup",e),this.trackExternalListener(i,"touchend",e)),this.on("match_requested",(t=>{const e=t.data,s=e.active?e.value:null,i=t.target.id;Object.values(this.panels).forEach((t=>{t.id!==i&&Object.values(t.data_layers).forEach((t=>t.destroyAllTooltips(!1)))})),this.applyState({lz_match_value:s})})),this._initialized=!0;const a=this.svg.node().getBoundingClientRect(),n=a.width?a.width:this.layout.width,o=a.height?a.height:this._total_height;return this.setDimensions(n,o),this}startDrag(t,e){t=t||null;let s=null;switch(e=e||null){case"background":case"x_tick":s="x";break;case"y1_tick":s="y1";break;case"y2_tick":s="y2"}if(!(t instanceof Dt&&s&&this._canInteract()))return this.stopDrag();const i=I.mouse(this.svg.node());return this._interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(s),dragging:{method:e,start_x:i[0],start_y:i[1],dragged_x:0,dragged_y:0,axis:s}},this.svg.style("cursor","all-scroll"),this}stopDrag(){const{_interaction:t}=this;if(!t.dragging)return this;if("object"!=typeof this.panels[t.panel_id])return this._interaction={},this;const e=this.panels[t.panel_id],s=(t,s,i)=>{e._data_layer_ids_by_z_index.forEach((a=>{const n=e.data_layers[a].layout[`${t}_axis`];n.axis===s&&(n.floor=i[0],n.ceiling=i[1],delete n.lower_buffer,delete n.upper_buffer,delete n.min_extent,delete n.ticks)}))};switch(t.dragging.method){case"background":case"x_tick":0!==t.dragging.dragged_x&&(s("x",1,e.x_extent),this.applyState({start:e.x_extent[0],end:e.x_extent[1]}));break;case"y1_tick":case"y2_tick":if(0!==t.dragging.dragged_y){const i=parseInt(t.dragging.method[1]);s("y",i,e[`y${i}_extent`])}}return this._interaction={},this.svg.style("cursor",null),this}get _total_height(){return this.layout.panels.reduce(((t,e)=>e.height+t),0)}}function qt(t,e,s){const i={0:"",3:"K",6:"M",9:"G"};if(s=s||!1,isNaN(e)||null===e){const s=Math.log(t)/Math.LN10;e=Math.min(Math.max(s-s%3,0),9)}const a=e-Math.floor((Math.log(t)/Math.LN10).toFixed(e+3)),n=Math.min(Math.max(e,0),2),o=Math.min(Math.max(a,n),12);let r=`${(t/Math.pow(10,e)).toFixed(o)}`;return s&&void 0!==i[e]&&(r+=` ${i[e]}b`),r}function Ft(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const s=/([KMG])[B]*$/,i=s.exec(e);let a=1;return i&&(a="M"===i[1]?1e6:"G"===i[1]?1e9:1e3,e=e.replace(s,"")),e=Number(e)*a,e}function Ht(t,e,s){if("object"!=typeof e)throw new Error("invalid arguments: data is not an object");if("string"!=typeof t)throw new Error("invalid arguments: html is not a string");const i=[],a=/{{(?:(#if )?([\w+_:|]+)|(#else)|(\/if))}}/;for(;t.length>0;){const e=a.exec(t);e?0!==e.index?(i.push({text:t.slice(0,e.index)}),t=t.slice(e.index)):"#if "===e[1]?(i.push({condition:e[2]}),t=t.slice(e[0].length)):e[2]?(i.push({variable:e[2]}),t=t.slice(e[0].length)):"#else"===e[3]?(i.push({branch:"else"}),t=t.slice(e[0].length)):"/if"===e[4]?(i.push({close:"if"}),t=t.slice(e[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(t)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([e[1],e[2],e[3]])}`),t=t.slice(e[0].length)):(i.push({text:t}),t="")}const n=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){let e=t.then=[];for(t.else=[];i.length>0;){if("if"===i[0].close){i.shift();break}"else"===i[0].branch&&(i.shift(),e=t.else),e.push(n())}return t}return console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(t)}`),{text:""}},o=[];for(;i.length>0;)o.push(n());const r=function(t){return Object.prototype.hasOwnProperty.call(r.cache,t)||(r.cache[t]=new K(t).resolve(e,s)),r.cache[t]};r.cache={};const l=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=r(t.variable);if(-1!==["string","number","boolean"].indexOf(typeof e))return e;if(null===e)return""}catch(e){console.error(`Error while processing variable ${JSON.stringify(t.variable)}`)}return`{{${t.variable}}}`}if(t.condition){try{if(r(t.condition))return t.then.map(l).join("");if(t.else)return t.else.map(l).join("")}catch(e){console.error(`Error while processing condition ${JSON.stringify(t.variable)}`)}return""}console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(t)}`)};return o.map(l).join("")}const Gt=new h;Gt.add("=",((t,e)=>t===e)),Gt.add("!=",((t,e)=>t!=e)),Gt.add("<",((t,e)=>tt<=e)),Gt.add(">",((t,e)=>t>e)),Gt.add(">=",((t,e)=>t>=e)),Gt.add("%",((t,e)=>t%e)),Gt.add("in",((t,e)=>e&&e.includes(t))),Gt.add("match",((t,e)=>t&&t.includes(e)));const Jt=Gt,Zt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,Kt=(t,e)=>{const s=t.breaks||[],i=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=s.reduce((function(t,s){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,Wt=(t,e,s)=>{const i=t.values;return i[s%i.length]};let Yt=(t,e,s)=>{const i=t._cache=t._cache||new Map,a=t.max_cache_size||500;if(i.size>=a&&i.clear(),i.has(e))return i.get(e);let n=0;e=String(e);for(let t=0;t{var s=t.breaks||[],i=t.values||[],a=t.null_value?t.null_value:null;if(s.length<2||s.length!==i.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return i[0];if(+e>=t.breaks[t.breaks.length-1])return i[s.length-1];{var n=null;if(s.forEach((function(t,i){i&&s[i-1]<=+e&&s[i]>=+e&&(n=i)})),null===n)return a;const t=(+e-s[n-1])/(s[n]-s[n-1]);return isFinite(t)?I.interpolate(i[n-1],i[n])(t):a}};function Qt(t,e){if(void 0===e)return null;const{beta_field:s,stderr_beta_field:i,"+":a=null,"-":n=null}=t;if(!s||!i)throw new Error("effect_direction must specify how to find required 'beta' and 'stderr_beta' fields");const o=e[s],r=e[i];if(void 0!==o)if(void 0!==r){if(o-1.96*r>0)return a;if(o+1.96*r<0)return n||null}else{if(o>0)return a;if(o<0)return n}return null}const te=new h;for(let[t,e]of Object.entries(n))te.add(t,e);te.add("if",Zt);const ee=te,se={id:"",type:"",tag:"custom_data_type",namespace:{},data_operations:[],id_field:"id",filters:null,match:{},x_axis:{},y_axis:{},legend:null,tooltip:{},tooltip_positioning:"horizontal",behaviors:{}};class ie{constructor(t,e){this._initialized=!1,this._layout_idx=null,this.id=null,this._base_id=null,this.parent=e||null,this.svg={},this.parent_plot=null,e&&(this.parent_plot=e.parent),this.layout=at(t||{},se),this.layout.id&&(this.id=this.layout.id),this._filter_func=null,this.layout.x_axis!=={}&&"number"!=typeof this.layout.x_axis.axis&&(this.layout.x_axis.axis=1),this.layout.y_axis!=={}&&"number"!=typeof this.layout.y_axis.axis&&(this.layout.y_axis.axis=1),this._base_layout=nt(this.layout),this.state={},this._state_id=null,this._layer_state=null,this._setDefaultState(),this.data=[],this.layout.tooltip&&(this._tooltips={}),this._global_statuses={highlighted:!1,selected:!1,faded:!1,hidden:!1},this._data_contract=new Set,this._entities=new Map,this._dependencies=[],this.mutateLayout()}render(){throw new Error("Method must be implemented")}moveForward(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e+1]&&(t[e]=t[e+1],t[e+1]=this.id,this.parent.resortDataLayers()),this}moveBack(){const t=this.parent._data_layer_ids_by_z_index,e=this.layout.z_index;return t[e-1]&&(t[e]=t[e-1],t[e-1]=this.id,this.parent.resortDataLayers()),this}setElementAnnotation(t,e,s){const i=this.getElementId(t);return this._layer_state.extra_fields[i]||(this._layer_state.extra_fields[i]={}),this._layer_state.extra_fields[i][e]=s,this}setFilter(t){console.warn("The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead"),this._filter_func=t}mutateLayout(){if(this.parent_plot){const{namespace:t,data_operations:e}=this.layout;this._data_contract=rt(this.layout,Object.keys(t));const[s,i]=this.parent_plot.lzd.config_to_sources(t,e,this);this._entities=s,this._dependencies=i}}_getDataExtent(t,e){return t=t||this.data,I.extent(t,(t=>+new K(e.field).resolve(t)))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const s=this.layout.id_field;let i=t[s];if(void 0===i&&/{{[^{}]*}}/.test(s)&&(i=Ht(s,t,{})),null==i)throw new Error("Unable to generate element ID");const a=i.toString().replace(/\W/g,""),n=`${this.getBaseId()}-${a}`.replace(/([:.[\],])/g,"_");return t[e]=n,n}getElementStatusNodeId(t){return null}getElementById(t){const e=I.select(`#${t.replace(/([:.[\],])/g,"\\$1")}`);return!e.empty()&&e.data()&&e.data().length?e.data()[0]:null}applyDataMethods(){const t=this.layout.match&&this.layout.match.receive,e=Jt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new K(t):null;if(this.data.length&&this._data_contract.size){const t=new Set(this._data_contract);for(let e of this.data)if(Object.keys(e).forEach((e=>t.delete(e))),!t.size)break;t.size&&console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...t]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in "data_operations" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`)}return this.data.forEach(((a,n)=>{t&&null!=s&&(a.lz_is_match=e(i.resolve(a),s)),a.getDataLayer=()=>this,a.getPanel=()=>this.parent||null,a.getPlot=()=>{const t=this.parent;return t?t.parent:null}})),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,s){let i=null;if(Array.isArray(t)){let a=0;for(;null===i&&ad-(p+v)?"top":"bottom"):"horizontal"===w&&(v=0,w=_<=r.width/2?"left":"right"),"top"===w||"bottom"===w){const t=Math.max(c.width/2-_,0),e=Math.max(c.width/2+_-u,0);y=h.x+_-c.width/2-e+t,b=h.x+_-y-7,"top"===w?(g=h.y+p-(v+c.height+8),f="down",m=c.height-1):(g=h.y+p+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+_+x+8,f="left",b=-8):(y=h.x+_-c.width-x-8,f="right",b=c.width-1),p-c.height/2<=0?(g=h.y+p-10.5-6,m=6):p+c.height/2>=d?(g=h.y+p+7+6-c.height,m=c.height-14-6):(g=h.y+p-c.height/2,m=c.height/2-7)}return t.selector.style("left",`${y}px`).style("top",`${g}px`),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class",`lz-data_layer-tooltip-arrow_${f}`).style("left",`${b}px`).style("top",`${m}px`),this}filter(t,e,s,i){let a=!0;return t.forEach((t=>{const{field:s,operator:i,value:n}=t,o=Jt.get(i),r=this.getElementAnnotation(e);o(s?new K(s).resolve(e,r):e,n)||(a=!1)})),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this._layer_state.extra_fields[s];return e?i&&i[e]:i}_applyFilters(t){return t=t||this.data,this._filter_func?t=t.filter(this._filter_func):this.layout.filters&&(t=t.filter(this.filter.bind(this,this.layout.filters))),t}_setDefaultState(){const t={status_flags:{},extra_fields:{}},e=t.status_flags;C.adjectives.forEach((t=>{e[t]=e[t]||new Set})),e.has_tooltip=e.has_tooltip||new Set,this.parent&&(this._state_id=`${this.parent.id}.${this.id}`,this.state=this.parent.state,this.state[this._state_id]=t),this._layer_state=t}getBaseId(){return this._base_id?this._base_id:this.parent?`${this.parent_plot.id}.${this.parent.id}.${this.id}`:(this.id||"").toString()}getAbsoluteDataHeight(){return this.svg.group.node().getBoundingClientRect().height}initialize(){this._base_id=this.getBaseId();const t=this.getBaseId();return this.svg.container=this.parent.svg.group.append("g").attr("class","lz-data_layer-container").attr("id",`${t}.data_layer_container`),this.svg.clipRect=this.svg.container.append("clipPath").attr("id",`${t}.clip`).append("rect"),this.svg.group=this.svg.container.append("g").attr("id",`${t}.data_layer`).attr("clip-path",`url(#${t}.clip)`),this}createTooltip(t){if("object"!=typeof this.layout.tooltip)throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);const e=this.getElementId(t);if(!this._tooltips[e])return this._tooltips[e]={data:t,arrow:null,selector:I.select(this.parent_plot.svg.node().parentNode).append("div").attr("class","lz-data_layer-tooltip").attr("id",`${e}-tooltip`)},this._layer_state.status_flags.has_tooltip.add(e),this.updateTooltip(t),this;this.positionTooltip(e)}updateTooltip(t,e){return void 0===e&&(e=this.getElementId(t)),this._tooltips[e].selector.html(""),this._tooltips[e].arrow=null,this.layout.tooltip.html&&this._tooltips[e].selector.html(Ht(this.layout.tooltip.html,t,this.getElementAnnotation(t))),this.layout.tooltip.closable&&this._tooltips[e].selector.insert("button",":first-child").attr("class","lz-tooltip-close-button").attr("title","Close").text("×").on("click",(()=>{this.destroyTooltip(e)})),this._tooltips[e].selector.data([t]),this.positionTooltip(e),this}destroyTooltip(t,e){let s;if(s="string"==typeof t?t:this.getElementId(t),this._tooltips[s]&&("object"==typeof this._tooltips[s].selector&&this._tooltips[s].selector.remove(),delete this._tooltips[s]),!e){this._layer_state.status_flags.has_tooltip.delete(s)}return this}destroyAllTooltips(t=!0){for(let e in this._tooltips)this.destroyTooltip(e,t);return this}positionTooltip(t){if("string"!=typeof t)throw new Error("Unable to position tooltip: id is not a string");if(!this._tooltips[t])throw new Error("Unable to position tooltip: id does not point to a valid tooltip");const e=this._tooltips[t],s=this._getTooltipPosition(e);if(!s)return null;this._drawTooltip(e,this.layout.tooltip_positioning,s.x_min,s.x_max,s.y_min,s.y_max)}positionAllTooltips(){for(let t in this._tooltips)this.positionTooltip(t);return this}showOrHideTooltip(t,e){const s=this.layout.tooltip;if("object"!=typeof s)return this;const i=this.getElementId(t),a=(t,e,s)=>{let i=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))s=s||"and",i=1===e.length?t[e[0]]:e.reduce(((e,i)=>"and"===s?t[e]&&t[i]:"or"===s?t[e]||t[i]:null));else{if("object"!=typeof e)return!1;{let n;for(let o in e)n=a(t,e[o],o),null===i?i=n:"and"===s?i=i&&n:"or"===s&&(i=i||n)}}return i};let n={};"string"==typeof s.show?n={and:[s.show]}:"object"==typeof s.show&&(n=s.show);let o={};"string"==typeof s.hide?o={and:[s.hide]}:"object"==typeof s.hide&&(o=s.hide);const r=this._layer_state;var l={};C.adjectives.forEach((t=>{const e=`un${t}`;l[t]=r.status_flags[t].has(i),l[e]=!l[t]}));const h=a(l,n),c=a(l,o),d=r.status_flags.has_tooltip.has(i);return!h||!e&&!d||c?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,s,i){if("has_tooltip"===t)return this;let a;void 0===s&&(s=!0);try{a=this.getElementId(e)}catch(t){return this}i&&this.setAllElementStatus(t,!s),I.select(`#${a}`).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const n=this.getElementStatusNodeId(e);null!==n&&I.select(`#${n}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const o=!this._layer_state.status_flags[t].has(a);s&&o&&this._layer_state.status_flags[t].add(a),s||o||this._layer_state.status_flags[t].delete(a),this.showOrHideTooltip(e,o),o&&this.parent.emit("layout_changed",!0);const r="selected"===t;!r||!o&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const l=this.layout.match&&this.layout.match.send;return!r||void 0===l||!o&&s||this.parent.emit("match_requested",{value:new K(l).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;if(void 0===e&&(e=!0),e)this.data.forEach((e=>this.setElementStatus(t,e,!0)));else{new Set(this._layer_state.status_flags[t]).forEach((e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)})),this._layer_state.status_flags[t]=new Set}return this._global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach((e=>{const s=/(click|mouseover|mouseout)/.exec(e);s&&t.on(`${s[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))}))}executeBehaviors(t,e){const s=t.includes("ctrl"),i=t.includes("shift"),a=this;return function(t){t=t||I.select(I.event.target).datum(),s===!!I.event.ctrlKey&&i===!!I.event.shiftKey&&e.forEach((e=>{if("object"==typeof e&&null!==e)switch(e.action){case"set":a.setElementStatus(e.status,t,!0,e.exclusive);break;case"unset":a.setElementStatus(e.status,t,!1,e.exclusive);break;case"toggle":var s=a._layer_state.status_flags[e.status].has(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=Ht(e.href,t,a.getElementAnnotation(t));"string"==typeof e.target?window.open(s,e.target):window.location.href=s}}}))}}_getPageOrigin(){const t=this.parent._getPageOrigin();return{x:t.x+this.parent.layout.margin.left,y:t.y+this.parent.layout.margin.top}}applyAllElementStatus(){const t=this._layer_state.status_flags,e=this;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&t[s].forEach((t=>{try{this.setElementStatus(s,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e._state_id}, ${s}`),console.error(t)}}))}draw(){return this.svg.container.attr("transform",`translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`),this.svg.clipRect.attr("width",this.parent.layout.cliparea.width).attr("height",this.parent.layout.cliparea.height),this.positionAllTooltips(),this}reMap(){return this.destroyAllTooltips(),this.parent_plot.lzd.getData(this.state,this._entities,this._dependencies).then((t=>{this.data=t,this.applyDataMethods(),this._initialized=!0,this.parent.emit("data_from_layer",{layer:this.getBaseId(),content:nt(t)},!0)}))}}C.verbs.forEach(((t,e)=>{const s=C.adjectives[e],i=`un${t}`;ie.prototype[`${t}Element`]=function(t,e=!1){return e=!!e,this.setElementStatus(s,t,!0,e),this},ie.prototype[`${i}Element`]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},ie.prototype[`${t}AllElements`]=function(){return this.setAllElementStatus(s,!0),this},ie.prototype[`${i}AllElements`]=function(){return this.setAllElementStatus(s,!1),this}}));const ae={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class ne extends ie{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");at(t,ae),super(...arguments)}initialize(){super.initialize(),this._hitareas_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-hit_areas`),this._visible_lines_group=this.svg.group.append("g").attr("class",`lz-data_layer-${this.layout.type}-visible_lines`)}render(){const t=this._applyFilters(),e=this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field])),s=(e,s)=>{const i=this.parent.x_scale(e[this.layout.x_axis.field]);let a=i-this.layout.hitarea_width/2;if(s>=1){const e=t[s-1],n=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(i+n)/2)}return[a,i]};e.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(e).attr("id",(t=>this.getElementId(t))).attr("height",this.parent.layout.height).attr("opacity",0).attr("x",((t,e)=>s(t,e)[0])).attr("width",((t,e)=>{const i=s(t,e);return i[1]-i[0]+this.layout.hitarea_width/2}));const i=this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(t,(t=>t[this.layout.id_field]));i.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(i).attr("id",(t=>this.getElementId(t))).attr("x",(t=>this.parent.x_scale(t[this.layout.x_axis.field])-.5)).attr("width",1).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))),i.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,s=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),i=e.x_scale(t.data[this.layout.x_axis.field]),a=s/2;return{x_min:i-1,x_max:i+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const oe={color:"#CCCCCC",fill_opacity:.5,filters:null,regions:[],id_field:"id",start_field:"start",end_field:"end",merge_field:null};class re extends ie{constructor(t){if(at(t,oe),t.interaction||t.behaviors)throw new Error("highlight_regions layer does not support mouse events");if(t.regions.length&&t.namespace&&Object.keys(t.namespace).length)throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');super(...arguments)}_mergeNodes(t){const{end_field:e,merge_field:s,start_field:i}=this.layout;if(!s)return t;t.sort(((t,e)=>I.ascending(t[s],e[s])||I.ascending(t[i],e[i])));let a=[];return t.forEach((function(t,n){const o=a[a.length-1]||t;if(t[s]===o[s]&&t[i]<=o[e]){const s=Math.min(o[i],t[i]),n=Math.max(o[e],t[e]);t=Object.assign({},o,t,{[i]:s,[e]:n}),a.pop()}a.push(t)})),a}render(){const{x_scale:t}=this.parent;let e=this.layout.regions.length?this.layout.regions:this.data;e.forEach(((t,e)=>t.id||(t.id=e))),e=this._applyFilters(e),e=this._mergeNodes(e);const s=this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`).data(e);s.enter().append("rect").attr("class",`lz-data_layer-${this.layout.type}`).merge(s).attr("id",(t=>this.getElementId(t))).attr("x",(e=>t(e[this.layout.start_field]))).attr("width",(e=>t(e[this.layout.end_field])-t(e[this.layout.start_field]))).attr("height",this.parent.layout.height).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))),s.exit().remove(),this.svg.group.style("pointer-events","none")}_getTooltipPosition(t){throw new Error("This layer does not support tooltips")}}const le={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class he extends ie{constructor(t){t=at(t,le),super(...arguments)}render(){const t=this,e=t.layout,s=t.parent.x_scale,i=t.parent[`y${e.y_axis.axis}_scale`],a=this._applyFilters();function n(t){const a=t[e.x_axis.field1],n=t[e.x_axis.field2],o=(a+n)/2,r=[[s(a),i(0)],[s(o),i(t[e.y_axis.field])],[s(n),i(0)]];return I.line().x((t=>t[0])).y((t=>t[1])).curve(I.curveNatural)(r)}const o=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(a,(t=>this.getElementId(t))),r=this.svg.group.selectAll("path.lz-data_layer-arcs").data(a,(t=>this.getElementId(t)));return this.svg.group.call(gt,e.style),o.enter().append("path").attr("class","lz-data_layer-arcs-hitarea").merge(o).attr("id",(t=>this.getElementId(t))).style("fill","none").style("stroke-width",e.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",(t=>n(t))),r.enter().append("path").attr("class","lz-data_layer-arcs").merge(r).attr("id",(t=>this.getElementId(t))).attr("stroke",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("d",((t,e)=>n(t))),r.exit().remove(),o.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,s=this.layout,i=t.data[s.x_axis.field1],a=t.data[s.x_axis.field2],n=e[`y${s.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(i,a)),x_max:e.x_scale(Math.max(i,a)),y_min:n(t.data[s.y_axis.field]),y_max:n(0)}}}const ce={stroke:"rgb(54, 54, 150)",color:"#363696",label_font_size:15,label_exon_spacing:3,exon_height:10,bounding_box_padding:3,track_vertical_spacing:5,tooltip_positioning:"top"};class de extends ie{constructor(t){t=at(t,ce),super(...arguments),this.transcript_idx=0,this.tracks=1,this.gene_track_index={1:[]}}getElementStatusNodeId(t){return`${this.getElementId(t)}-statusnode`}getTrackHeight(){return 2*this.layout.bounding_box_padding+this.layout.label_font_size+this.layout.label_exon_spacing+this.layout.exon_height+this.layout.track_vertical_spacing}assignTracks(t){const e=(t,e)=>{try{const s=this.svg.group.append("text").attr("x",0).attr("y",0).attr("class","lz-data_layer-genes lz-label").style("font-size",e).text(`${t}→`),i=s.node().getBBox().width;return s.remove(),i}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.filter((t=>!(t.endthis.state.end))).map((t=>{if(t.gene_id&&t.gene_id.indexOf(".")){const e=t.gene_id.split(".");t.gene_id=e[0],t.gene_version=e[1]}if(t.transcript_id=t.transcripts[this.transcript_idx].transcript_id,t.display_range={start:this.parent.x_scale(Math.max(t.start,this.state.start)),end:this.parent.x_scale(Math.min(t.end,this.state.end))},t.display_range.label_width=e(t.gene_name,this.layout.label_font_size),t.display_range.width=t.display_range.end-t.display_range.start,t.display_range.text_anchor="middle",t.display_range.widththis.state.end)t.display_range.start=t.display_range.end-t.display_range.label_width-this.layout.label_font_size,t.display_range.text_anchor="end";else{const e=(t.display_range.label_width-t.display_range.width)/2+this.layout.label_font_size;t.display_range.start-ethis.parent.x_scale(this.state.end)?(t.display_range.end=this.parent.x_scale(this.state.end),t.display_range.start=t.display_range.end-t.display_range.label_width,t.display_range.text_anchor="end"):(t.display_range.start-=e,t.display_range.end+=e)}t.display_range.width=t.display_range.end-t.display_range.start}t.display_range.start-=this.layout.bounding_box_padding,t.display_range.end+=this.layout.bounding_box_padding,t.display_range.width+=2*this.layout.bounding_box_padding,t.display_domain={start:this.parent.x_scale.invert(t.display_range.start),end:this.parent.x_scale.invert(t.display_range.end)},t.display_domain.width=t.display_domain.end-t.display_domain.start,t.track=null;let s=1;for(;null===t.track;){let e=!1;this.gene_track_index[s].map((s=>{if(!e){const i=Math.min(s.display_range.start,t.display_range.start);Math.max(s.display_range.end,t.display_range.end)-ithis.tracks&&(this.tracks=s,this.gene_track_index[s]=[])):(t.track=s,this.gene_track_index[s].push(t))}return t.parent=this,t.transcripts.map(((e,s)=>{t.transcripts[s].parent=t,t.transcripts[s].exons.map(((e,i)=>t.transcripts[s].exons[i].parent=t.transcripts[s]))})),t}))}render(){const t=this;let e,s=this._applyFilters();s=this.assignTracks(s);const i=this.svg.group.selectAll("g.lz-data_layer-genes").data(s,(t=>t.gene_name));i.enter().append("g").attr("class","lz-data_layer-genes").merge(i).attr("id",(t=>this.getElementId(t))).each((function(s){const i=s.parent,a=I.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([s],(t=>i.getElementStatusNodeId(t)));e=i.getTrackHeight()-i.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",(t=>i.getElementStatusNodeId(t))).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),a.exit().remove();const n=I.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([s],(t=>`${t.gene_name}_boundary`));e=1,n.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(n).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing+Math.max(i.layout.exon_height,3)/2)).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e,s))),n.exit().remove();const o=I.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([s],(t=>`${t.gene_name}_label`));o.enter().append("text").attr("class","lz-data_layer-genes lz-label").merge(o).attr("text-anchor",(t=>t.display_range.text_anchor)).text((t=>"+"===t.strand?`${t.gene_name}→`:`←${t.gene_name}`)).style("font-size",s.parent.layout.label_font_size).attr("x",(t=>"middle"===t.display_range.text_anchor?t.display_range.start+t.display_range.width/2:"start"===t.display_range.text_anchor?t.display_range.start+i.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-i.layout.bounding_box_padding:void 0)).attr("y",(t=>(t.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size)),o.exit().remove();const r=I.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(s.transcripts[s.parent.transcript_idx].exons,(t=>t.exon_id));e=i.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",((e,s)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,s))).style("stroke",((e,s)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,s))).attr("width",(t=>i.parent.x_scale(t.end)-i.parent.x_scale(t.start))).attr("height",e).attr("x",(t=>i.parent.x_scale(t.start))).attr("y",(()=>(s.track-1)*i.getTrackHeight()+i.layout.bounding_box_padding+i.layout.label_font_size+i.layout.label_exon_spacing)),r.exit().remove();const l=I.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([s],(t=>`${t.gene_name}_clickarea`));e=i.getTrackHeight()-i.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",(t=>`${i.getElementId(t)}_clickarea`)).attr("rx",i.layout.bounding_box_padding).attr("ry",i.layout.bounding_box_padding).attr("width",(t=>t.display_range.width)).attr("height",e).attr("x",(t=>t.display_range.start)).attr("y",(t=>(t.track-1)*i.getTrackHeight())),l.exit().remove()})),i.exit().remove(),this.svg.group.on("click.event_emitter",(t=>this.parent.emit("element_clicked",t,!0))).call(this.applyBehaviors.bind(this))}_getTooltipPosition(t){const e=this.getElementStatusNodeId(t.data),s=I.select(`#${e}`).node().getBBox();return{x_min:this.parent.x_scale(t.data.start),x_max:this.parent.x_scale(t.data.end),y_min:s.y,y_max:s.y+s.height}}}const ue={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5,tooltip:null};class _e extends ie{constructor(t){if((t=at(t,ue)).tooltip)throw new Error("The line / filled curve layer does not support tooltips");super(...arguments)}render(){const t=this.parent,e=this.layout.x_axis.field,s=this.layout.y_axis.field,i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=i.enter().append("path").attr("class","lz-data_layer-line");const n=t.x_scale,o=t[`y${this.layout.y_axis.axis}_scale`];a=this.layout.style.fill&&"none"!==this.layout.style.fill?I.area().x((t=>+n(t[e]))).y0(+o(0)).y1((t=>+o(t[s]))):I.line().x((t=>+n(t[e]))).y((t=>+o(t[s]))).curve(I[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(gt,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!C.adjectives.includes(t))throw new Error("Invalid status");if(void 0===this._layer_state.status_flags[t])return this;void 0===e&&(e=!0),this._global_statuses[t]=e;let s="lz-data_layer-line";return Object.keys(this._global_statuses).forEach((t=>{this._global_statuses[t]&&(s+=` lz-data_layer-line-${t}`)})),this.path.attr("class",s),this.parent.emit("layout_changed",!0),this}}const pe={style:{stroke:"#D3D3D3","stroke-width":"3px","stroke-dasharray":"10px 10px"},orientation:"horizontal",x_axis:{axis:1,decoupled:!0},y_axis:{axis:1,decoupled:!0},tooltip_positioning:"vertical",offset:0};class ge extends ie{constructor(t){t=at(t,pe),["horizontal","vertical"].includes(t.orientation)||(t.orientation="horizontal"),super(...arguments)}getElementId(t){return this.getBaseId()}render(){const t=this.parent,e=`y${this.layout.y_axis.axis}_scale`,s=`y${this.layout.y_axis.axis}_extent`;if("horizontal"===this.layout.orientation)this.data=[{x:t.x_extent[0],y:this.layout.offset},{x:t.x_extent[1],y:this.layout.offset}];else{if("vertical"!==this.layout.orientation)throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');this.data=[{x:this.layout.offset,y:t[s][0]},{x:this.layout.offset,y:t[s][1]}]}const i=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],n=I.line().x(((e,s)=>{const i=+t.x_scale(e.x);return isNaN(i)?t.x_range[s]:i})).y(((s,i)=>{const n=+t[e](s.y);return isNaN(n)?a[i]:n}));this.path=i.enter().append("path").attr("class","lz-data_layer-line").merge(i).attr("d",n).call(gt,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=I.mouse(this.svg.container.node()),e=t[0],s=t[1];return{x_min:e-1,x_max:e+1,y_min:s-1,y_max:s+1}}catch(t){return null}}}const ye={point_size:40,point_shape:"circle",tooltip_positioning:"horizontal",color:"#888888",coalesce:{active:!1,max_points:800,x_min:"-Infinity",x_max:"Infinity",y_min:0,y_max:3,x_gap:7,y_gap:7},fill_opacity:1,y_axis:{axis:1},id_field:"id"};class fe extends ie{constructor(t){(t=at(t,ye)).label&&isNaN(t.label.spacing)&&(t.label.spacing=4),super(...arguments)}_getTooltipPosition(t){const e=this.parent.x_scale(t.data[this.layout.x_axis.field]),s=`y${this.layout.y_axis.axis}_scale`,i=this.parent[s](t.data[this.layout.y_axis.field]),a=this.resolveScalableParameter(this.layout.point_size,t.data),n=Math.sqrt(a/Math.PI);return{x_min:e-n,x_max:e+n,y_min:i-n,y_max:i+n}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),s=t.layout.label.spacing,i=Boolean(t.layout.label.lines),a=2*s,n=this.parent_plot.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*s,o=(t,a)=>{const n=+t.attr("x"),o=2*s+2*Math.sqrt(e);let r,l;i&&(r=+a.attr("x2"),l=s+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",n-o),i&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",n+o),i&&a.attr("x2",r+l))};t._label_texts.each((function(e,a){const r=I.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>n){const e=i?I.select(t._label_lines.nodes()[a]):null;o(r,e)}})),t._label_texts.each((function(e,n){const r=I.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?I.select(t._label_lines.nodes()[n]):null;t._label_texts.each((function(){const t=I.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(o(r,c),l=+r.attr("x"),l-h.width-sl.left&&r.topl.top))return;s=!0;const h=o.attr("y"),c=.5*(r.topp?(g=d-+n,d=+n,u-=g):u+l.height/2>p&&(g=u-+h,u=+h,d-=g),a.attr("y",d),o.attr("y",u)}))})),s){if(t.layout.label.lines){const e=t._label_texts.nodes();t._label_lines.attr("y2",((t,s)=>I.select(e[s]).attr("y")))}this._label_iterations<150&&setTimeout((()=>{this.separate_labels()}),1)}}render(){const t=this,e=this.parent.x_scale,s=this.parent[`y${this.layout.y_axis.axis}_scale`],i=Symbol.for("lzX"),a=Symbol.for("lzY");let n=this._applyFilters();if(n.forEach((t=>{let n=e(t[this.layout.x_axis.field]),o=s(t[this.layout.y_axis.field]);isNaN(n)&&(n=-1e3),isNaN(o)&&(o=-1e3),t[i]=n,t[a]=o})),this.layout.coalesce.active&&n.length>this.layout.coalesce.max_points){let{x_min:t,x_max:i,y_min:a,y_max:o,x_gap:r,y_gap:l}=this.layout.coalesce;n=function(t,e,s,i,a,n,o){let r=[];const l=Symbol.for("lzX"),h=Symbol.for("lzY");let c=null,d=null,u=[];function _(){if(u.length){const t=u[Math.floor((u.length-1)/2)];r.push(t)}c=d=null,u=[]}function p(t,e,s){c=t,d=e,u.push(s)}return t.forEach((t=>{const g=t[l],y=t[h],f=g>=e&&g<=s&&y>=a&&y<=n;t.lz_is_match||!f?(_(),r.push(t)):null===c?p(g,y,t):Math.abs(g-c)<=i&&Math.abs(y-d)<=o?u.push(t):(_(),p(g,y,t))})),_(),r}(n,isFinite(t)?e(+t):-1/0,isFinite(i)?e(+i):1/0,r,isFinite(o)?s(+o):-1/0,isFinite(a)?s(+a):1/0,l)}if(this.layout.label){let e;const s=t.layout.label.filters||[];if(s.length){const t=this.filter.bind(this,s);e=n.filter(t)}else e=n;this._label_groups=this.svg.group.selectAll(`g.lz-data_layer-${this.layout.type}-label`).data(e,(t=>`${t[this.layout.id_field]}_label`));const o=`lz-data_layer-${this.layout.type}-label`,r=this._label_groups.enter().append("g").attr("class",o);this._label_texts&&this._label_texts.remove(),this._label_texts=this._label_groups.merge(r).append("text").text((e=>Ht(t.layout.label.text||"",e,this.getElementAnnotation(e)))).attr("x",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing)).attr("y",(t=>t[a])).attr("text-anchor","start").call(gt,t.layout.label.style||{}),t.layout.label.lines&&(this._label_lines&&this._label_lines.remove(),this._label_lines=this._label_groups.merge(r).append("line").attr("x1",(t=>t[i])).attr("y1",(t=>t[a])).attr("x2",(e=>e[i]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2)).attr("y2",(t=>t[a])).call(gt,t.layout.label.lines.style||{})),this._label_groups.exit().remove()}else this._label_texts&&this._label_texts.remove(),this._label_lines&&this._label_lines.remove(),this._label_groups&&this._label_groups.remove();const o=this.svg.group.selectAll(`path.lz-data_layer-${this.layout.type}`).data(n,(t=>t[this.layout.id_field])),r=I.symbol().size(((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e))).type(((t,e)=>ot(this.resolveScalableParameter(this.layout.point_shape,t,e)))),l=`lz-data_layer-${this.layout.type}`;o.enter().append("path").attr("class",l).attr("id",(t=>this.getElementId(t))).merge(o).attr("transform",(t=>`translate(${t[i]}, ${t[a]})`)).attr("fill",((t,e)=>this.resolveScalableParameter(this.layout.color,t,e))).attr("fill-opacity",((t,e)=>this.resolveScalableParameter(this.layout.fill_opacity,t,e))).attr("d",r),o.exit().remove(),this.layout.label&&(this.flip_labels(),this._label_iterations=0,this.separate_labels()),this.svg.group.on("click.event_emitter",(()=>{const t=I.select(I.event.target).datum();this.parent.emit("element_clicked",t,!0)})).call(this.applyBehaviors.bind(this))}makeLDReference(t){let e=null;if(void 0===t)throw new Error("makeLDReference requires one argument of any type");return e="object"==typeof t?this.layout.id_field&&void 0!==t[this.layout.id_field]?t[this.layout.id_field].toString():void 0!==t.id?t.id.toString():t.toString():t.toString(),this.parent.emit("set_ldrefvar",{ldrefvar:e},!0),this.parent_plot.applyState({ldrefvar:e})}}class me extends fe{constructor(t){super(...arguments),this._categories={}}_prepareData(){const t=this.layout.x_axis.field||"x",e=this.layout.x_axis.category_field;if(!e)throw new Error(`Layout for ${this.layout.id} must specify category_field`);const s=this.data.sort(((t,s)=>{const i=t[e],a=s[e],n="string"==typeof i?i.toLowerCase():i,o="string"==typeof a?a.toLowerCase():a;return n===o?0:n{e[t]=e[t]||s})),s}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",s={};this.data.forEach((i=>{const a=i[t],n=i[e],o=s[a]||[n,n];s[a]=[Math.min(o[0],n),Math.max(o[1],n)]}));const i=Object.keys(s);return this._setDynamicColorScheme(i),s}_getColorScale(t){let e=(t=t||this.layout).color||[];if(Array.isArray(e)&&(e=e.find((t=>"categorical_bin"===t.scale_function))),!e||"categorical_bin"!==e.scale_function)throw new Error("This layer requires that color options be provided as a `categorical_bin`");return e}_setDynamicColorScheme(t){const e=this._getColorScale(this.layout).parameters,s=this._getColorScale(this._base_layout).parameters;if(s.categories.length&&s.values.length){const i={};s.categories.forEach((t=>{i[t]=1})),t.every((t=>Object.prototype.hasOwnProperty.call(i,t)))?e.categories=s.categories:e.categories=t}else e.categories=t;let i;for(i=s.values.length?s.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];i.length{const o=i[t];let r;switch(s){case"left":r=o[0];break;case"center":const t=o[1]-o[0];r=o[0]+(0!==t?t:o[0])/2;break;case"right":r=o[1]}return{x:r,text:t,style:{fill:a[e.indexOf(t)]||"#000000"}}}))}}applyCustomDataMethods(){return this.data=this._prepareData(),this._categories=this._generateCategoryBounds(),this}}const be=new c;for(let[t,e]of Object.entries(o))be.add(t,e);const xe=be,ve=7.301,we={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{assoc:variant|htmlescape}}
                                                                                                                                                                                                                                                                                \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
                                                                                                                                                                                                                                                                                \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
                                                                                                                                                                                                                                                                                \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
                                                                                                                                                                                                                                                                                '},$e=function(){const t=nt(we);return t.html+="{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label",t}(),ze={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'

                                                                                                                                                                                                                                                                                {{gene_name|htmlescape}}

                                                                                                                                                                                                                                                                                Gene ID: {{gene_id|htmlescape}}
                                                                                                                                                                                                                                                                                Transcript ID: {{transcript_id|htmlescape}}
                                                                                                                                                                                                                                                                                {{#if pLI}}
                                                                                                                                                                                                                                                                                ConstraintExpected variantsObserved variantsConst. Metric
                                                                                                                                                                                                                                                                                Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
                                                                                                                                                                                                                                                                                o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
                                                                                                                                                                                                                                                                                Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
                                                                                                                                                                                                                                                                                o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
                                                                                                                                                                                                                                                                                pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
                                                                                                                                                                                                                                                                                o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

                                                                                                                                                                                                                                                                                {{/if}}More data on gnomAD'},ke={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:'{{catalog:variant|htmlescape}}
                                                                                                                                                                                                                                                                                Catalog entries: {{n_catalog_matches|htmlescape}}
                                                                                                                                                                                                                                                                                Top Trait: {{catalog:trait|htmlescape}}
                                                                                                                                                                                                                                                                                Top P Value: {{catalog:log_pvalue|logtoscinotation}}
                                                                                                                                                                                                                                                                                More: GWAS catalog / dbSNP'},Ee={closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Regulatory element
                                                                                                                                                                                                                                                                                {{access:start1|htmlescape}}-{{access:end1|htmlescape}}
                                                                                                                                                                                                                                                                                Promoter
                                                                                                                                                                                                                                                                                {{access:start2|htmlescape}}-{{access:end2|htmlescape}}
                                                                                                                                                                                                                                                                                {{#if access:target}}Target: {{access:target|htmlescape}}
                                                                                                                                                                                                                                                                                {{/if}}Score: {{access:score|htmlescape}}"},Me={id:"significance",type:"orthogonal_line",tag:"significance",orientation:"horizontal",offset:ve},Se={id:"recombrate",namespace:{recomb:"recomb"},data_operations:[{type:"fetch",from:["recomb"]}],type:"line",tag:"recombination",z_index:1,style:{stroke:"#0000FF","stroke-width":"1.5px"},x_axis:{field:"recomb:position"},y_axis:{axis:2,field:"recomb:recomb_rate",floor:0,ceiling:100}},Ne={namespace:{assoc:"assoc",ld:"ld"},data_operations:[{type:"fetch",from:["assoc","ld(assoc)"]},{type:"left_match",name:"assoc_plus_ld",requires:["assoc","ld"],params:["assoc:position","ld:position2"]}],id:"associationpvalues",type:"scatter",tag:"association",id_field:"assoc:variant",coalesce:{active:!0},point_shape:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"diamond"}},{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"assoc:beta",stderr_beta_field:"assoc:se"}},"circle"],point_size:{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:80,else:40}},color:[{scale_function:"if",field:"lz_is_ld_refvar",parameters:{field_value:!0,then:"#9632b8"}},{scale_function:"numerical_bin",field:"ld:correlation",parameters:{breaks:[0,.2,.4,.6,.8],values:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"]}},"#AAAAAA"],legend:[{label:"LD (r²)",label_size:14},{shape:"ribbon",orientation:"vertical",width:10,height:15,color_stops:["rgb(70, 54, 153)","rgb(38, 188, 225)","rgb(110, 254, 104)","rgb(248, 195, 42)","rgb(219, 61, 17)"],tick_labels:[0,.2,.4,.6,.8,1]}],label:null,z_index:2,x_axis:{field:"assoc:position"},y_axis:{axis:1,field:"assoc:log_pvalue",floor:0,upper_buffer:.1,min_extent:[0,10]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(we)},Ae={id:"coaccessibility",type:"arcs",tag:"coaccessibility",namespace:{access:"access"},data_operations:[{type:"fetch",from:["access"]}],match:{send:"access:target",receive:"access:target"},id_field:"{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}",filters:[{field:"access:score",operator:"!=",value:null}],color:[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},{scale_function:"ordinal_cycle",parameters:{values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]}}],x_axis:{field1:"access:start1",field2:"access:start2"},y_axis:{axis:1,field:"access:score",upper_buffer:.1,min_extent:[0,1]},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(Ee)},Oe=function(){let t=nt(Ne);return t=at({id:"associationpvaluescatalog",fill_opacity:.7},t),t.data_operations.push({type:"assoc_to_gwas_catalog",name:"assoc_catalog",requires:["assoc_plus_ld","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}),t.tooltip.html+='{{#if catalog:rsid}}
                                                                                                                                                                                                                                                                                See hits in GWAS catalog{{/if}}',t.namespace.catalog="catalog",t}(),Te={id:"phewaspvalues",type:"category_scatter",tag:"phewas",namespace:{phewas:"phewas"},data_operations:[{type:"fetch",from:["phewas"]}],point_shape:[{scale_function:"effect_direction",parameters:{"+":"triangle","-":"triangledown",beta_field:"phewas:beta",stderr_beta_field:"phewas:se"}},"circle"],point_size:70,tooltip_positioning:"vertical",id_field:"{{phewas:trait_group}}_{{phewas:trait_label}}",x_axis:{field:"lz_auto_x",category_field:"phewas:trait_group",lower_buffer:.025,upper_buffer:.025},y_axis:{axis:1,field:"phewas:log_pvalue",floor:0,upper_buffer:.15},color:[{field:"phewas:trait_group",scale_function:"categorical_bin",parameters:{categories:[],values:[],null_value:"#B8B8B8"}}],fill_opacity:.7,tooltip:{closable:!0,show:{or:["highlighted","selected"]},hide:{and:["unhighlighted","unselected"]},html:"Trait: {{phewas:trait_label|htmlescape}}
                                                                                                                                                                                                                                                                                \nTrait Category: {{phewas:trait_group|htmlescape}}
                                                                                                                                                                                                                                                                                \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
                                                                                                                                                                                                                                                                                β: {{phewas:beta|scinotation|htmlescape}}
                                                                                                                                                                                                                                                                                {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}"},behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},label:{text:"{{phewas:trait_label}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"phewas:log_pvalue",operator:">=",value:20}],style:{"font-size":"14px","font-weight":"bold",fill:"#333333"}}},Le={namespace:{gene:"gene",constraint:"constraint"},data_operations:[{type:"fetch",from:["gene","constraint(gene)"]},{name:"gene_constraint",type:"genes_to_gnomad_constraint",requires:["gene","constraint"]}],id:"genes",type:"genes",tag:"genes",id_field:"gene_id",behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ze)},je=at({filters:[{field:"gene_type",operator:"in",value:["protein_coding","IG_C_gene","IG_D_gene","IG_J_gene","IG_V_gene","TR_C_gene","TR_D_gene","TR_J_gene","TR_V_gene","rRNA","Mt_rRNA","Mt_tRNA"]}]},nt(Le)),Pe={namespace:{assoc:"assoc",catalog:"catalog"},data_operations:[{type:"fetch",from:["assoc","catalog"]},{type:"assoc_to_gwas_catalog",name:"assoc_plus_ld",requires:["assoc","catalog"],params:["assoc:position","catalog:pos","catalog:log_pvalue"]}],id:"annotation_catalog",type:"annotation_track",tag:"gwascatalog",id_field:"assoc:variant",x_axis:{field:"assoc:position"},color:"#0000CC",filters:[{field:"catalog:rsid",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:nt(ke),tooltip_positioning:"top"},Re={type:"set_state",tag:"ld_population",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",custom_event_name:"widget_set_ldpop",state_field:"ld_pop",options:[{display_name:"ALL (default)",value:"ALL"},{display_name:"AFR",value:"AFR"},{display_name:"AMR",value:"AMR"},{display_name:"EAS",value:"EAS"},{display_name:"EUR",value:"EUR"},{display_name:"SAS",value:"SAS"}]},Ie={type:"display_options",tag:"gene_filter",custom_event_name:"widget_gene_filter_choice",position:"right",color:"blue",button_html:"Filter...",button_title:"Choose which genes to show",layer_name:"genes",default_config_display_name:"Coding genes & rRNA",options:[{display_name:"All features",display:{filters:null}}]},Ce={widgets:[{type:"remove_panel",position:"right",color:"red",group_position:"end"},{type:"move_panel_up",position:"right",group_position:"middle"},{type:"move_panel_down",position:"right",group_position:"start",style:{"margin-left":"0.75em"}}]},De={widgets:[{type:"title",title:"LocusZoom",subtitle:'v0.14.0',position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Be=function(){const t=nt(De);return t.widgets.push(nt(Re)),t}(),Ue=function(){const t=nt(De);return t.widgets.push({type:"shift_region",step:5e5,button_html:">>",position:"right",group_position:"end"},{type:"shift_region",step:5e4,button_html:">",position:"right",group_position:"middle"},{type:"zoom_region",step:.2,position:"right",group_position:"middle"},{type:"zoom_region",step:-.2,position:"right",group_position:"middle"},{type:"shift_region",step:-5e4,button_html:"<",position:"right",group_position:"middle"},{type:"shift_region",step:-5e5,button_html:"<<",position:"right",group_position:"start"}),t}(),qe={id:"association",tag:"association",min_height:200,height:300,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"toggle_legend",position:"right"}),t}(),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"-log10 p-value",label_offset:50},y2:{label:"Recombination Rate (cM/Mb)",label_offset:46}},legend:{orientation:"vertical",origin:{x:75,y:40},hidden:!0},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,drag_y2_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Me),nt(Se),nt(Ne)]},Fe={id:"coaccessibility",tag:"coaccessibility",min_height:150,height:180,margin:{top:35,right:55,bottom:40,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"},y1:{label:"Score",label_offset:40,render:!1}},interaction:{drag_background_to_pan:!0,drag_x_ticks_to_scale:!0,drag_y1_ticks_to_scale:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Ae)]},He=function(){let t=nt(qe);return t=at({id:"associationcatalog"},t),t.toolbar.widgets.push({type:"display_options",position:"right",color:"blue",button_html:"Display options...",button_title:"Control how plot items are displayed",layer_name:"associationpvaluescatalog",default_config_display_name:"No catalog labels (default)",options:[{display_name:"Label catalog traits",display:{label:{text:"{{catalog:trait}}",spacing:6,lines:{style:{"stroke-width":"2px",stroke:"#333333","stroke-dasharray":"2px 2px"}},filters:[{field:"catalog:trait",operator:"!=",value:null},{field:"catalog:log_pvalue",operator:">",value:ve},{field:"ld:correlation",operator:">",value:.4}],style:{"font-size":"12px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[nt(Me),nt(Se),nt(Oe)],t}(),Ge={id:"genes",tag:"genes",min_height:150,height:225,margin:{top:20,right:55,bottom:20,left:70},axes:{},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},toolbar:function(){const t=nt(Ce);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},nt(Ie)),t}(),data_layers:[nt(je)]},Je={id:"phewas",tag:"phewas",min_height:300,height:300,margin:{top:20,right:55,bottom:120,left:70},inner_border:"rgb(210, 210, 210)",axes:{x:{ticks:{style:{"font-weight":"bold","font-size":"11px","text-anchor":"start"},transform:"rotate(50)",position:"left"}},y1:{label:"-log10 p-value",label_offset:50}},data_layers:[nt(Me),nt(Te)]},Ze={id:"annotationcatalog",tag:"gwascatalog",min_height:50,height:50,margin:{top:25,right:55,bottom:10,left:70},inner_border:"rgb(210, 210, 210)",toolbar:nt(Ce),axes:{x:{extent:"state",render:!1}},interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[nt(Pe)]},Ke={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[nt(qe),nt(Ge)]},Ve={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Be,panels:[Ze,He,Ge]},We={width:800,responsive_resize:!0,toolbar:De,panels:[nt(Je),at({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:38,tick_format:"region",extent:"state"}}},nt(Ge))],mouse_guide:!1},Ye={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:nt(De),panels:[nt(Fe),function(){const t=Object.assign({height:270},nt(Ge)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const s=[{field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#4285f4"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},Xe={standard_association:we,standard_association_with_label:$e,standard_genes:ze,catalog_variant:ke,coaccessibility:Ee},Qe={ldlz2_pop_selector:Re,gene_selector_menu:Ie},ts={standard_panel:Ce,standard_plot:De,standard_association:Be,region_nav_plot:Ue},es={significance:Me,recomb_rate:Se,association_pvalues:Ne,coaccessibility:Ae,association_pvalues_catalog:Oe,phewas_pvalues:Te,genes:Le,genes_filtered:je,annotation_catalog:Pe},ss={association:qe,coaccessibility:Fe,association_catalog:He,genes:Ge,phewas:Je,annotation_catalog:Ze},is={standard_association:Ke,association_catalog:Ve,standard_phewas:We,coaccessibility:Ye};const as=new class extends h{get(t,e,s={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let i=super.get(t).get(e);const a=s.namespace;i.namespace||delete s.namespace;let n=at(s,i);return a&&(n=it(n,a)),nt(n)}add(t,e,s,i=!1){if(!(t&&e&&s))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof s)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new h);const a=nt(s);return"data_layer"===t&&a.namespace&&(a._auto_fields=[...rt(a,Object.keys(a.namespace))].sort()),super.get(t).add(e,a,i)}list(t){if(!t){let t={};for(let[e,s]of this._items)t[e]=s.list();return t}return super.get(t).list()}merge(t,e){return at(t,e)}renameField(){return lt(...arguments)}mutate_attrs(){return ht(...arguments)}query_attrs(){return ct(...arguments)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))as.add(t,s,i);const ns=as,os=new h;function rs(t){return(e,s,...i)=>{if(2!==s.length)throw new Error("Join functions must receive exactly two recordsets");return t(...s,...i)}}os.add("left_match",rs(x)),os.add("inner_match",rs((function(t,e,s,i){return b("inner",...arguments)}))),os.add("full_outer_match",rs((function(t,e,s,i){return b("outer",...arguments)}))),os.add("assoc_to_gwas_catalog",rs((function(t,e,s,i,a){if(!t.length)return t;const n=m(e,i),o=[];for(let t of n.values()){let e,s=0;for(let i of t){const t=i[a];t>=s&&(e=i,s=t)}e.n_catalog_matches=t.length,o.push(e)}return x(t,o,s,i)}))),os.add("genes_to_gnomad_constraint",rs((function(t,e){return t.forEach((function(t){const s=`_${t.gene_name.replace(/[^A-Za-z0-9_]/g,"_")}`,i=e[s]&&e[s].gnomad_constraint;i&&Object.keys(i).forEach((function(e){let s=i[e];void 0===t[e]&&("number"==typeof s&&s.toString().includes(".")&&(s=parseFloat(s.toFixed(2))),t[e]=s)}))})),t})));const ls=os;const hs={version:l,populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return I.select(t).html(""),I.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!I.select(`#lz-${e}`).empty();)e++;t.attr("id",`#lz-${e}`)}if(i=new Ut(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){const e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/;let s=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(s){if("+"===s[3]){const t=Ft(s[2]),e=Ft(s[4]);return{chr:s[1],start:t-e,end:t+e}}return{chr:s[1],start:Ft(s[2]),end:Ft(s[4])}}if(s=e.exec(t),s)return{chr:s[1],position:Ft(s[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=I.select(`div#${i.id}`).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",`${i.id}_svg`).attr("class","lz-locuszoom").call(gt,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends h{constructor(t){super(),this._registry=t||R}add(t,e,s=!1){if(this._registry.has(t))throw new Error(`The namespace ${t} is already in use by another source`);if(t.match(/[^A-Za-z0-9_]/))throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${t}`);if(Array.isArray(e)){const[t,s]=e;e=this._registry.create(t,s)}return e.source_id=t,super.add(t,e,s),this}},Adapters:R,DataLayers:xe,DataFunctions:ls,Layouts:ns,MatchFunctions:Jt,ScaleFunctions:ee,TransformationFunctions:Z,Widgets:jt,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),R}},cs=[];hs.use=function(t,...e){if(!cs.includes(t)){if(e.unshift(hs),"function"==typeof t.install)t.install.apply(t,e);else{if("function"!=typeof t)throw new Error("Plugin must export a function that receives the LocusZoom object as an argument");t.apply(null,e)}cs.push(t)}};const ds=hs})(),LocusZoom=i.default})(); //# sourceMappingURL=locuszoom.app.min.js.map \ No newline at end of file diff --git a/dist/locuszoom.app.min.js.map b/dist/locuszoom.app.min.js.map index 40090508..a443f5a9 100644 --- a/dist/locuszoom.app.min.js.map +++ b/dist/locuszoom.app.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/./node_modules/@hapi/hoek/lib/assert.js","webpack://[name]/./node_modules/@hapi/hoek/lib/error.js","webpack://[name]/./node_modules/@hapi/hoek/lib/stringify.js","webpack://[name]/./node_modules/@hapi/topo/lib/index.js","webpack://[name]/./node_modules/just-clone/index.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./esm/data/undercomplicate/lru_cache.js","webpack://[name]/./esm/data/undercomplicate/util.js","webpack://[name]/./esm/data/undercomplicate/requests.js","webpack://[name]/./esm/data/undercomplicate/joins.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./esm/data/undercomplicate/adapter.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/registry/data_ops.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["AssertError","module","exports","condition","args","length","Error","Stringify","super","filter","arg","map","message","join","captureStackTrace","this","assert","JSON","stringify","apply","err","Assert","internals","_items","nodes","options","before","concat","after","group","sort","includes","Array","isArray","node","item","seq","push","manual","valid","_sort","others","other","Object","assign","mergeSort","i","graph","graphAfters","create","groups","expandedGroups","graphNodeItem","ancestors","children","child","visited","sorted","next","j","shouldSeeCount","seenCount","k","seqIndex","value","sortedItem","a","b","getRegExpFlags","regExp","source","flags","global","ignoreCase","multiline","sticky","unicode","clone","obj","result","key","type","toString","call","slice","Date","getTime","RegExp","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","prop","prototype","hasOwnProperty","r","Symbol","toStringTag","RegistryBase","Map","name","has","override","set","delete","from","keys","ClassRegistry","parent_name","source_name","overrides","console","warn","arguments","base","sub","add","LLNode","metadata","prev","LRUCache","max_size","_max_size","_cur_size","_store","_head","_tail","cached","prior","_remove","old","match_callback","data","getLinkedData","shared_options","entities","dependencies","consolidate","parsed","spec","exec","name_alone","name_deps","deps","split","_parse_declaration","dag","toposort","entries","e","order","responses","provider","depends_on","this_result","Promise","all","then","prior_results","_provider_name","getData","values","all_results","groupBy","records","group_key","item_group","_any_match","left","right","left_key","right_key","right_index","results","left_match_value","right_matches","right_item","left_index","right_match_value","left_match","REGEX_MARKER","parseMarker","test","match","BaseApiAdapter","BaseLZAdapter","config","_config","cache_enabled","cache_size","_enable_cache","_cache","dependent_data","response_text","_buildRequestOptions","cache_key","_getCacheKey","resolve","_performRequest","text","_normalizeResponse","_cache_meta","catch","remove","_annotateRecords","_postProcessResponse","_url","url","_getURL","fetch","response","ok","statusText","parse","params","prefix_namespace","limit_fields","_prefix_namespace","_limit_fields","Set","chr","start","end","superset","find","md","row","reduce","acc","label","a_record","fieldname","suffixer","BaseUMAdapter","_genome_build","genome_build","build","constructor","N","every","fields","record","AssociationLZ","_source_id","request_options","GwasCatalogLZ","_validateBuildSource","source_query","GeneLZ","GeneConstraintLZ","state","genes_data","unique_gene_names","gene","gene_name","query","replace","body","method","headers","LDServer","assoc_data","assoc_variant_name","_findPrefixedKey","assoc_logp_name","refvar","best_hit","ldrefvar","best_logp","variant","log_pvalue","lz_is_ld_refvar","chrom","pos","ref","alt","coord","String","__find_ld_refvar","_skip_request","ld_refvar","ld_source","ld_population","ld_pop","population","encodeURIComponent","combined","chainRequests","payload","forEach","RecombLZ","StaticSource","_data","PheWASLZ","registry","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","transforms","full_name","field_name","transformations","val","transform","extra","undefined","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","m","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","paths","p","_","__","subject","uniqPaths","arr","elem","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","layout","shared_namespaces","requested_ns","merge","custom_layout","default_layout","property","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","findFields","prefixes","field_finder","all_ns","value_type","a_match","renameField","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","regex","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","DataOperation","join_type","initiator","_callable","_initiator","_params","plot_state","dependent_recordsets","data_layer","sources","_sources","namespace_options","data_operations","namespace_local_names","dependency_order","unshift","ns_pattern","local_name","global_name","dep_spec","requires","namecount","require_name","task","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","parentNode","insert","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","x","width","delay","setTimeout","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","position","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","index","indexOf","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","element","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","_panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","radioId","has_option","choice","defaultName","default_config_display_name","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","addWidget","widget","error","_panel_boundaries","dragging","_interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","line_height","_data_layer_ids_by_z_index","reverse","layer_legend","label_x","label_y","shape_factory","path_y","is_horizontal","color_stops","all_elements","ribbon_group","axis_group","axis_offset","tick_labels","range","scale","domain","axis","tickSize","tickValues","tickFormat","v","to_next_marking","radius","PI","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","_initialized","_layout_idx","_state_id","_data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","_zoom_timeout","_event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","parseFloat","y_axis","z_index","dlid","idx","layout_idx","data_layer_layout","target_layer","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","axes_config","base_x_range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","current_drag","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","namespace","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tick_format","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","panel_boundaries","mouse_guide","Plot","datasource","_remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","_layer_state","_setDefaultState","target_panel","clearPanelData","opts","success_callback","from_layer","onerror","error_callback","base_prefix","layer_target","startsWith","is_valid_layer","some","listener","config_to_sources","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","mutateLayout","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","resize_listener","window","requestAnimationFrame","rescaleSVG","addEventListener","trackExternalListener","IntersectionObserver","threshold","observer","entry","intersectionRatio","observe","load_listener","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","_mouse_guide","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","ret","positionStringToInt","suffixre","mult","tokens","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","field_value","numerical_bin","breaks","null_value","curr","categorical_bin","categories","ordinal_cycle","stable_choice","max_cache_size","clear","hash","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","effect_direction","input","beta_field","stderr_beta_field","plus_result","neg_result","beta_val","se_val","id_field","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","_tooltips","_global_statuses","_data_contract","_entities","_dependencies","layer_order","current_index","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","fields_unseen","debug","lz_is_match","getDataLayer","getPanel","getPlot","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","tooltip_layout","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","_label_texts","da","dal","_label_lines","dax","abound","bbound","_label_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","_label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","version","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","custom_namespaces","_auto_fields","contents","list","_wrap_join","handle","catalog_data","assoc_key","catalog_key","catalog_logp_name","catalog_by_variant","catalog_flat","claims","best_variant","best","n_catalog_matches","constraint_data","alias","constraint","LocusZoom","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","source_id","Adapters","DataLayers","DataFunctions","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["'use strict';\n\nconst AssertError = require('./error');\n\nconst internals = {};\n\n\nmodule.exports = function (condition, ...args) {\n\n if (condition) {\n return;\n }\n\n if (args.length === 1 &&\n args[0] instanceof Error) {\n\n throw args[0];\n }\n\n throw new AssertError(args);\n};\n","'use strict';\n\nconst Stringify = require('./stringify');\n\n\nconst internals = {};\n\n\nmodule.exports = class extends Error {\n\n constructor(args) {\n\n const msgs = args\n .filter((arg) => arg !== '')\n .map((arg) => {\n\n return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);\n });\n\n super(msgs.join(' ') || 'Unknown error');\n\n if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$\n Error.captureStackTrace(this, exports.assert);\n }\n }\n};\n","'use strict';\n\nconst internals = {};\n\n\nmodule.exports = function (...args) {\n\n try {\n return JSON.stringify.apply(null, args);\n }\n catch (err) {\n return '[Cannot display object: ' + err.message + ']';\n }\n};\n","'use strict';\n\nconst Assert = require('@hapi/hoek/lib/assert');\n\n\nconst internals = {};\n\n\nexports.Sorter = class {\n\n constructor() {\n\n this._items = [];\n this.nodes = [];\n }\n\n add(nodes, options) {\n\n options = options || {};\n\n // Validate rules\n\n const before = [].concat(options.before || []);\n const after = [].concat(options.after || []);\n const group = options.group || '?';\n const sort = options.sort || 0; // Used for merging only\n\n Assert(!before.includes(group), `Item cannot come before itself: ${group}`);\n Assert(!before.includes('?'), 'Item cannot come before unassociated items');\n Assert(!after.includes(group), `Item cannot come after itself: ${group}`);\n Assert(!after.includes('?'), 'Item cannot come after unassociated items');\n\n if (!Array.isArray(nodes)) {\n nodes = [nodes];\n }\n\n for (const node of nodes) {\n const item = {\n seq: this._items.length,\n sort,\n before,\n after,\n group,\n node\n };\n\n this._items.push(item);\n }\n\n // Insert event\n\n if (!options.manual) {\n const valid = this._sort();\n Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');\n }\n\n return this.nodes;\n }\n\n merge(others) {\n\n if (!Array.isArray(others)) {\n others = [others];\n }\n\n for (const other of others) {\n if (other) {\n for (const item of other._items) {\n this._items.push(Object.assign({}, item)); // Shallow cloned\n }\n }\n }\n\n // Sort items\n\n this._items.sort(internals.mergeSort);\n for (let i = 0; i < this._items.length; ++i) {\n this._items[i].seq = i;\n }\n\n const valid = this._sort();\n Assert(valid, 'merge created a dependencies error');\n\n return this.nodes;\n }\n\n sort() {\n\n const valid = this._sort();\n Assert(valid, 'sort created a dependencies error');\n\n return this.nodes;\n }\n\n _sort() {\n\n // Construct graph\n\n const graph = {};\n const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives\n const groups = Object.create(null);\n\n for (const item of this._items) {\n const seq = item.seq; // Unique across all items\n const group = item.group;\n\n // Determine Groups\n\n groups[group] = groups[group] || [];\n groups[group].push(seq);\n\n // Build intermediary graph using 'before'\n\n graph[seq] = item.before;\n\n // Build second intermediary graph with 'after'\n\n for (const after of item.after) {\n graphAfters[after] = graphAfters[after] || [];\n graphAfters[after].push(seq);\n }\n }\n\n // Expand intermediary graph\n\n for (const node in graph) {\n const expandedGroups = [];\n\n for (const graphNodeItem in graph[node]) {\n const group = graph[node][graphNodeItem];\n groups[group] = groups[group] || [];\n expandedGroups.push(...groups[group]);\n }\n\n graph[node] = expandedGroups;\n }\n\n // Merge intermediary graph using graphAfters into final graph\n\n for (const group in graphAfters) {\n if (groups[group]) {\n for (const node of groups[group]) {\n graph[node].push(...graphAfters[group]);\n }\n }\n }\n\n // Compile ancestors\n\n const ancestors = {};\n for (const node in graph) {\n const children = graph[node];\n for (const child of children) {\n ancestors[child] = ancestors[child] || [];\n ancestors[child].push(node);\n }\n }\n\n // Topo sort\n\n const visited = {};\n const sorted = [];\n\n for (let i = 0; i < this._items.length; ++i) { // Looping through item.seq values out of order\n let next = i;\n\n if (ancestors[i]) {\n next = null;\n for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values\n if (visited[j] === true) {\n continue;\n }\n\n if (!ancestors[j]) {\n ancestors[j] = [];\n }\n\n const shouldSeeCount = ancestors[j].length;\n let seenCount = 0;\n for (let k = 0; k < shouldSeeCount; ++k) {\n if (visited[ancestors[j][k]]) {\n ++seenCount;\n }\n }\n\n if (seenCount === shouldSeeCount) {\n next = j;\n break;\n }\n }\n }\n\n if (next !== null) {\n visited[next] = true;\n sorted.push(next);\n }\n }\n\n if (sorted.length !== this._items.length) {\n return false;\n }\n\n const seqIndex = {};\n for (const item of this._items) {\n seqIndex[item.seq] = item;\n }\n\n this._items = [];\n this.nodes = [];\n\n for (const value of sorted) {\n const sortedItem = seqIndex[value];\n this.nodes.push(sortedItem.node);\n this._items.push(sortedItem);\n }\n\n return true;\n }\n};\n\n\ninternals.mergeSort = (a, b) => {\n\n return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);\n};\n","module.exports = clone;\n\n/*\n Deep clones all properties except functions\n\n var arr = [1, 2, 3];\n var subObj = {aa: 1};\n var obj = {a: 3, b: 5, c: arr, d: subObj};\n var objClone = clone(obj);\n arr.push(4);\n subObj.bb = 2;\n obj; // {a: 3, b: 5, c: [1, 2, 3, 4], d: {aa: 1}}\n objClone; // {a: 3, b: 5, c: [1, 2, 3], d: {aa: 1, bb: 2}}\n*/\n\nfunction clone(obj) {\n if (typeof obj == 'function') {\n return obj;\n }\n var result = Array.isArray(obj) ? [] : {};\n for (var key in obj) {\n // include prototype properties\n var value = obj[key];\n var type = {}.toString.call(value).slice(8, -1);\n if (type == 'Array' || type == 'Object') {\n result[key] = clone(value);\n } else if (type == 'Date') {\n result[key] = new Date(value.getTime());\n } else if (type == 'RegExp') {\n result[key] = RegExp(value.source, getRegExpFlags(value));\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction getRegExpFlags(regExp) {\n if (typeof regExp.source.flags == 'string') {\n return regExp.source.flags;\n } else {\n var flags = [];\n regExp.global && flags.push('g');\n regExp.ignoreCase && flags.push('i');\n regExp.multiline && flags.push('m');\n regExp.sticky && flags.push('y');\n regExp.unicode && flags.push('u');\n return flags.join('');\n }\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.14.0-beta.4';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","/**\n * Implement an LRU Cache\n */\n\n\nclass LLNode {\n /**\n * A single node in the linked list. Users will only need to deal with this class if using \"approximate match\" (`cache.find()`)\n * @memberOf module:undercomplicate\n * @param {string} key\n * @param {*} value\n * @param {object} metadata\n * @param {LLNode} prev\n * @param {LLNode} next\n */\n constructor(key, value, metadata = {}, prev = null, next = null) {\n this.key = key;\n this.value = value;\n this.metadata = metadata;\n this.prev = prev;\n this.next = next;\n }\n}\n\nclass LRUCache {\n /**\n * Least-recently used cache implementation, with \"approximate match\" semantics\n * @memberOf module:undercomplicate\n * @param {number} [max_size=3]\n */\n constructor(max_size = 3) {\n this._max_size = max_size;\n this._cur_size = 0; // replace with map.size so we aren't managing manually?\n this._store = new Map();\n\n // Track LRU state\n this._head = null;\n this._tail = null;\n\n // Validate options\n if (max_size === null || max_size < 0) {\n throw new Error('Cache \"max_size\" must be >= 0');\n }\n }\n\n /**\n * Check key membership without updating LRU\n * @param key\n * @returns {boolean}\n */\n has(key) {\n return this._store.has(key);\n }\n\n /**\n * Retrieve value from cache (if present) and update LRU cache to say an item was recently used\n * @param key\n * @returns {null|*}\n */\n get(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return null;\n }\n if (this._head !== cached) {\n // Rewrite the cached value to ensure it is head of the list\n this.add(key, cached.value);\n }\n return cached.value;\n }\n\n /**\n * Add an item. Forcibly replaces the existing cached value for the same key.\n * @param key\n * @param value\n * @param {Object} [metadata={}) Metadata associated with an item. Metadata can be used for lookups (`cache.find`) to test for a cache hit based on non-exact match\n */\n add(key, value, metadata = {}) {\n if (this._max_size === 0) {\n // Don't cache items if cache has 0 size.\n return;\n }\n\n const prior = this._store.get(key);\n if (prior) {\n this._remove(prior);\n }\n\n const node = new LLNode(key, value, metadata, null, this._head);\n\n if (this._head) {\n this._head.prev = node;\n } else {\n this._tail = node;\n }\n\n this._head = node;\n this._store.set(key, node);\n\n if (this._max_size >= 0 && this._cur_size >= this._max_size) {\n const old = this._tail;\n this._tail = this._tail.prev;\n this._remove(old);\n }\n this._cur_size += 1;\n }\n\n\n // Cache manipulation methods\n clear() {\n this._head = null;\n this._tail = null;\n this._cur_size = 0;\n this._store = new Map();\n }\n\n // Public method, remove by key\n remove(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return false;\n }\n this._remove(cached);\n return true;\n }\n\n // Internal implementation, useful when working on list\n _remove(node) {\n if (node.prev !== null) {\n node.prev.next = node.next;\n } else {\n this._head = node.next;\n }\n\n if (node.next !== null) {\n node.next.prev = node.prev;\n } else {\n this._tail = node.prev;\n }\n this._store.delete(node.key);\n this._cur_size -= 1;\n }\n\n /**\n * Find a matching item in the cache, or return null. This is useful for \"approximate match\" semantics,\n * to check if something qualifies as a cache hit despite not having the exact same key.\n * (Example: zooming into a region, where the smaller region is a subset of something already cached)\n * @param {function} match_callback A function to be used to test the node as a possible match (returns true or false)\n * @returns {null|LLNode}\n */\n find(match_callback) {\n let node = this._head;\n while (node) {\n const next = node.next;\n if (match_callback(node)) {\n return node;\n }\n node = next;\n }\n }\n}\n\nexport { LRUCache };\n","/**\n * @private\n */\n\nimport justclone from 'just-clone';\n\n/**\n * The \"just-clone\" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.\n * @param {object} data\n * @returns {*}\n */\nfunction clone(data) {\n if (typeof data !== 'object') {\n return data;\n }\n return justclone(data);\n}\n\nexport { clone };\n","/**\n * Perform a series of requests, respecting order of operations\n * @private\n */\n\nimport {Sorter} from '@hapi/topo';\n\n\nfunction _parse_declaration(spec) {\n // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.\n const parsed = /^(?\\w+)$|((?\\w+)+\\(\\s*(?[^)]+?)\\s*\\))/.exec(spec);\n if (!parsed) {\n throw new Error(`Unable to parse dependency specification: ${spec}`);\n }\n\n let {name_alone, name_deps, deps} = parsed.groups;\n if (name_alone) {\n return [name_alone, []];\n }\n\n deps = deps.split(/\\s*,\\s*/);\n return [name_deps, deps];\n}\n\n/**\n * Perform a request for data from a set of providers, taking into account dependencies between requests.\n * This can be a mix of network requests or other actions (like join tasks), provided that the provider be some\n * object that implements a method `instance.getData`\n *\n * Each data layer in LocusZoom will translate the internal configuration into a format used by this function.\n * This function is never called directly in custom user code. In locuszoom, Requester Handles You\n *\n * TODO Future: It would be great to add a warning if the final element in the DAG does not reference all dependencies. This is a limitation of the current toposort library we use.\n *\n * @param {object} shared_options Options passed globally to all requests. In LocusZoom, this is often a copy of \"plot.state\"\n * @param {Map} entities A lookup of named entities that implement the method `instance.getData -> Promise`\n * @param {String[]} dependencies A description of how to fetch entities, and what they depend on, like `['assoc', 'ld(assoc)']`.\n * **Order will be determined by a DAG and the last item in the DAG is all that is returned.**\n * @param {boolean} [consolidate=true] Whether to return all results (almost never used), or just the last item in the resolved DAG.\n * This can be a pitfall in common usage: if you forget a \"join/consolidate\" task, the last result may appear to be missing some data.\n * @returns {Promise}\n */\nfunction getLinkedData(shared_options, entities, dependencies, consolidate = true) {\n if (!dependencies.length) {\n return [];\n }\n\n const parsed = dependencies.map((spec) => _parse_declaration(spec));\n const dag = new Map(parsed);\n\n // Define the order to perform requests in, based on a DAG\n const toposort = new Sorter();\n for (let [name, deps] of dag.entries()) {\n try {\n toposort.add(name, {after: deps, group: name});\n } catch (e) {\n throw new Error(`Invalid or possible circular dependency specification for: ${name}`);\n }\n }\n const order = toposort.nodes;\n\n // Verify that all requested entities exist by name!\n const responses = new Map();\n for (let name of order) {\n const provider = entities.get(name);\n if (!provider) {\n throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);\n }\n\n // Each promise should only be triggered when the things it depends on have been resolved\n const depends_on = dag.get(name) || [];\n const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));\n\n const this_result = prereq_promises.then((prior_results) => {\n // Each request will be told the name of the provider that requested it. This can be used during post-processing,\n // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)\n // This has a secondary effect: it ensures that any changes made to \"shared\" options in one adapter will\n // not leak out to others via a mutable shared object reference.\n const options = Object.assign({_provider_name: name}, shared_options);\n return provider.getData(options, ...prior_results);\n });\n responses.set(name, this_result);\n }\n return Promise.all([...responses.values()])\n .then((all_results) => {\n if (consolidate) {\n // Some usages- eg fetch + data join tasks- will only require the last response in the sequence\n // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)\n return all_results[all_results.length - 1];\n }\n return all_results;\n });\n}\n\n\nexport {getLinkedData};\n\n// For testing only\nexport {_parse_declaration};\n","/**\n * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.\n */\nimport { clone } from './util';\n\n\n/**\n * Simple grouping function, used to identify sets of records for joining.\n *\n * Used internally by join helpers, exported mainly for unit testing\n * @memberOf module:undercomplicate\n * @param {object[]} records\n * @param {string} group_key\n * @returns {Map}\n */\nfunction groupBy(records, group_key) {\n const result = new Map();\n for (let item of records) {\n const item_group = item[group_key];\n\n if (typeof item_group === 'undefined') {\n throw new Error(`All records must specify a value for the field \"${group_key}\"`);\n }\n if (typeof item_group === 'object') {\n // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)\n throw new Error('Attempted to group on a field with non-primitive values');\n }\n\n let group = result.get(item_group);\n if (!group) {\n group = [];\n result.set(item_group, group);\n }\n group.push(item);\n }\n return result;\n}\n\n\nfunction _any_match(type, left, right, left_key, right_key) {\n // Helper that consolidates logic for all three join types\n const right_index = groupBy(right, right_key);\n const results = [];\n for (let item of left) {\n const left_match_value = item[left_key];\n const right_matches = right_index.get(left_match_value) || [];\n if (right_matches.length) {\n // Record appears on both left and right; equiv to an inner join\n results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));\n } else if (type !== 'inner') {\n // Record appears on left but not right\n results.push(clone(item));\n }\n }\n\n if (type === 'outer') {\n // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side\n const left_index = groupBy(left, left_key);\n for (let item of right) {\n const right_match_value = item[right_key];\n const left_matches = left_index.get(right_match_value) || [];\n if (!left_matches.length) {\n results.push(clone(item));\n }\n }\n }\n return results;\n}\n\n/**\n * Equivalent to LEFT OUTER JOIN in SQL. Return all left records, joined to matching right data where appropriate.\n * @memberOf module:undercomplicate\n * @param {Object[]} left The left side recordset\n * @param {Object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction left_match(left, right, left_key, right_key) {\n return _any_match('left', ...arguments);\n}\n\n/**\n * Equivalent to INNER JOIN in SQL. Only return record joins if the key field has a match on both left and right.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction inner_match(left, right, left_key, right_key) {\n return _any_match('inner', ...arguments);\n}\n\n/**\n * Equivalent to FULL OUTER JOIN in SQL. Return records in either recordset, joined where appropriate.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction full_outer_match(left, right, left_key, right_key) {\n return _any_match('outer', ...arguments);\n}\n\nexport {left_match, inner_match, full_outer_match, groupBy};\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 1 }]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 2 }]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nimport {BaseUrlAdapter} from './undercomplicate';\n\nimport {parseMarker} from '../helpers/parse';\n\n/**\n * Replaced with the BaseLZAdapter class.\n * @public\n * @deprecated\n */\nclass BaseAdapter {\n constructor() {\n throw new Error('The \"BaseAdapter\" and \"BaseApiAdapter\" classes have been replaced in LocusZoom 0.14. See migration guide for details.');\n }\n}\n\n/**\n * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @deprecated\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {}\n\n\n/**\n * @extends module:undercomplicate.BaseUrlAdapter\n * @inheritDoc\n */\nclass BaseLZAdapter extends BaseUrlAdapter {\n /**\n * @param [config.cache_enabled=true]\n * @param [config.cache_size=3]\n * @param [config.url]\n * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name.\n * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant)\n * Typically, this is only disabled if the response payload is very unusual\n * @param {String[]} [config.limit_fields=null] If an API returns far more data than is needed, this can be used to simplify\n * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.\n */\n constructor(config = {}) {\n if (config.params) {\n // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places.\n console.warn('Deprecation warning: all options in \"config.params\" should now be specified as top level keys.');\n Object.assign(config, config.params || {});\n delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality!\n }\n super(config);\n\n // Prefix the namespace for this source to all fieldnames: id -> assoc.id\n // This is useful for almost all layers because the layout object says where to find every field, exactly.\n // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on\n // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear\n // in the response. (gene_name instead of genes.gene_name)\n const { prefix_namespace = true, limit_fields } = config;\n this._prefix_namespace = prefix_namespace;\n this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields\n }\n\n /**\n * Determine how a particular request will be identified in cache. Most LZ requests are region based,\n * so the default is a string concatenation of `chr_start_end`. This adapter is \"region aware\"- if the user\n * zooms in, it won't trigger a network request because we alread have the data needed.\n * @param options Receives plot.state plus any other request options defined by this source\n * @returns {string}\n * @public\n */\n _getCacheKey(options) {\n // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default\n let {chr, start, end} = options; // Current view: plot.state\n\n // Does a prior cache hit qualify as a superset of the ROI?\n const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end);\n if (superset) {\n ({ chr, start, end } = superset.metadata);\n }\n\n // The default cache key is region-based, and this method only returns the region-based part of the cache hit\n // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check\n options._cache_meta = { chr, start, end };\n return `${chr}_${start}_${end}`;\n }\n\n /**\n * Add the \"local namespace\" as a prefix for every field returned for this request. Eg if the association api\n * returns a field called variant, and the source is referred to as \"assoc\" within a particular data layer, then\n * the returned records will have a field called \"assoc:variant\"\n *\n * @param records\n * @param options\n * @returns {*}\n * @public\n */\n _postProcessResponse(records, options) {\n if (!this._prefix_namespace || !Array.isArray(records)) {\n return records;\n }\n\n // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for\n // assoc data might see \"variant\" as \"assoc.variant\"\n const { _limit_fields } = this;\n const { _provider_name } = options;\n\n return records.map((row) => {\n return Object.entries(row).reduce(\n (acc, [label, value]) => {\n // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload.\n if (!_limit_fields || _limit_fields.has(label)) {\n acc[`${_provider_name}:${label}`] = value;\n }\n return acc;\n },\n {},\n );\n });\n }\n\n /**\n * Convenience method, manually called in LZ sources that deal with dependent data.\n *\n * In the last step of fetching data, LZ adds a prefix to each field name.\n * This means that operations like \"build query based on prior data\" can't just ask for \"log_pvalue\" because\n * they are receiving \"assoc:log_pvalue\" or some such unknown prefix.\n *\n * This helper lets us use dependent data more easily. Not every adapter needs to use this method.\n *\n * @param {Object} a_record One record (often the first one in a set of records)\n * @param {String} fieldname The desired fieldname, eg \"log_pvalue\"\n */\n _findPrefixedKey(a_record, fieldname) {\n const suffixer = new RegExp(`:${fieldname}$`);\n const match = Object.keys(a_record).find((key) => suffixer.test(key));\n if (!match) {\n throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`);\n }\n return match;\n }\n}\n\n\n/**\n * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies\n * of one particular web server.\n * @extends module:LocusZoom_Adapters~BaseLZAdapter\n * @inheritDoc\n */\nclass BaseUMAdapter extends BaseLZAdapter {\n /**\n * @param {Object} config\n * @param {String} [config.build] The genome build to be used by all requests for this adapter. (UMich APIs are all genome build aware). \"GRCh37\" or \"GRCh38\"\n */\n constructor(config = {}) {\n super(config);\n // The UM portaldev API accepts an (optional) parameter \"genome_build\"\n this._genome_build = config.genome_build || config.build;\n }\n\n _validateBuildSource(build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${this.constructor.name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`);\n }\n }\n\n // Special behavior for the UM portaldev API: col -> row format normalization\n /**\n * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.\n * @param response_text\n * @param options\n * @returns {Object[]}\n * @public\n */\n _normalizeResponse(response_text, options) {\n let data = super._normalizeResponse(...arguments);\n // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload\n data = data.data || data;\n\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the columns, and create an object for each row record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n}\n\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @inheritDoc\n *\n * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL\n */\nclass AssociationLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files\n const { source } = config;\n this._source_id = source;\n }\n\n _getURL (request_options) {\n const {chr, start, end} = request_options;\n const base = super._getURL(request_options);\n return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`;\n }\n}\n\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GwasCatalogLZ extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build] The genome build to use when requesting the specific genomic region.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n const source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given.\n // We will avoid transforming or modifying the payload.\n this._prefix_namespace = false;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GeneConstraintLZ extends BaseLZAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config = {}) {\n super(config);\n this._prefix_namespace = false;\n }\n\n _buildRequestOptions(state, genes_data) {\n const build = state.genome_build || this._config.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = new Set();\n for (let gene of genes_data) {\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n unique_gene_names.add(gene.gene_name);\n }\n\n state.query = [...unique_gene_names.values()].map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n state.build = build;\n return Object.assign({}, state);\n }\n\n _performRequest(options) {\n let {query, build} = options;\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears to set max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve([]);\n }\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n\n const url = this._getURL(options);\n\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.\n */\n _normalizeResponse(response_text) {\n if (typeof response_text !== 'string') {\n // If the query short-circuits, we receive an empty list instead of a string\n return response_text;\n }\n const data = JSON.parse(response_text);\n return data.data;\n }\n}\n\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant and yse that as the LD reference variant.\n *\n * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly\n * if this information is not provided.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass LDServer extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.method='rsquare'] The metric used to calculate LD\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n __find_ld_refvar(state, assoc_data) {\n const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant');\n const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue');\n\n // Determine the reference variant (via user selected OR automatic-per-track)\n let refvar;\n let best_hit = {};\n if (state.ldrefvar) {\n // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data\n refvar = state.ldrefvar;\n best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {};\n } else {\n // find highest log-value and associated var spec\n let best_logp = 0;\n for (let item of assoc_data) {\n const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item;\n if (log_pvalue > best_logp) {\n best_logp = log_pvalue;\n refvar = variant;\n best_hit = item;\n }\n }\n }\n\n // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting.\n // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase.\n best_hit.lz_is_ld_refvar = true;\n\n // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server,\n // the variant fields must be normalized to a specific format. All later LD operations will use that format.\n const match = parseMarker(refvar, true);\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n\n const [chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip?\n if (ref && alt) {\n refvar += `_${ref}/${alt}`;\n }\n\n const coord = +pos;\n // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably\n // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby.\n if ((coord && state.ldrefvar && state.chr) && (chrom !== String(state.chr) || coord < state.start || coord > state.end)) {\n // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a\n // *copy* of plot.state, so wiping here doesn't remove the original value.\n state.ldrefvar = null;\n return this.__find_ld_refvar(state, assoc_data);\n }\n\n // Return the reference variant, in a normalized format suitable for LDServer queries\n return refvar;\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n // No variants, so no need to annotate association data with LD!\n // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region)\n base._skip_request = true;\n return base;\n }\n\n base.ld_refvar = this.__find_ld_refvar(state, assoc_data);\n\n // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config\n const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let ld_source = state.ld_source || this._config.source || '1000G';\n const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL\n\n if (ld_source === '1000G' && genome_build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n ld_source = '1000G-FRZ09';\n }\n\n this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option\n return Object.assign({}, base, { genome_build, ld_source, ld_population });\n }\n\n _getURL(request_options) {\n const method = this._config.method || 'rsquare';\n const {\n chr, start, end,\n ld_refvar,\n genome_build, ld_source, ld_population,\n } = request_options;\n\n const base = super._getURL(request_options);\n\n return [\n base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(ld_refvar),\n '&chrom=', encodeURIComponent(chr),\n '&start=', encodeURIComponent(start),\n '&stop=', encodeURIComponent(end),\n ].join('');\n }\n\n _getCacheKey(options) {\n // LD is keyed by more than just region; append other parameters to the base cache key\n const base = super._getCacheKey(options);\n const { ld_refvar, ld_source, ld_population } = options;\n return `${base}_${ld_refvar}_${ld_source}_${ld_population}`;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird.\n return Promise.resolve([]);\n }\n\n const url = this._getURL(options);\n\n // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseUMAdapter {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['position', 'recomb_rate'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {object} config.data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseLZAdapter {\n constructor(config = {}) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n super(...arguments);\n const { data } = config;\n if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key\n throw new Error(\"'StaticSource' must provide data as required option 'config.data'\");\n }\n this._data = data;\n }\n\n _performRequest(options) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will\n * be used to provide PheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const base = super._getURL(request_options);\n const url = [\n base,\n \"?filter=variant eq '\", encodeURIComponent(request_options.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n _getCacheKey(options) {\n // Not a region based source; don't do anything smart for cache check\n return this._getURL(options);\n }\n}\n\n// Deprecated symbols\nexport { BaseAdapter, BaseApiAdapter };\n\n// Usually used as a parent class for custom code\nexport { BaseLZAdapter, BaseUMAdapter };\n\n// Usually used as a standalone class\nexport {\n AssociationLZ,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","import {LRUCache} from './lru_cache';\nimport {clone} from './util';\n\n/**\n * @param {boolean} [config.cache_enabled=true] Whether to enable the LRU cache, and store a copy of the normalized and parsed response data.\n * Turned on by default for most remote requests; turn off if you are using another datastore (like Vuex) or if the response body uses too much memory.\n * @param {number} [config.cache_size=3] How many requests to cache. Track-dependent annotations like LD might benefit\n * from caching more items, while very large payloads (like, um, TOPMED LD) might benefit from a smaller cache size.\n * For most LocusZoom usages, the cache is \"region aware\": zooming in will use cached data, not a separate request\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseAdapter {\n constructor(config = {}) {\n this._config = config;\n const {\n // Cache control\n cache_enabled = true,\n cache_size = 3,\n } = config;\n this._enable_cache = cache_enabled;\n this._cache = new LRUCache(cache_size);\n }\n\n /**\n * Build an object with options that control the request. This can take into account both explicit options, and prior data.\n * @param {Object} options Any global options passed in via `getData`. Eg, in locuszoom, every request is passed a copy of `plot.state` as the options object, in which case every adapter would expect certain basic information like `chr, start, end` to be available.\n * @param {Object[]} dependent_data If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, `ld(assoc)` means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.\n * @returns {*} An options object containing initial options, plus any calculated values relevant to the request.\n * @public\n */\n _buildRequestOptions(options, dependent_data) {\n // Perform any pre-processing required that may influence the request. Receives an array with the payloads\n // for each request that preceded this one in the dependency chain\n // This method may optionally take dependent data into account. For many simple adapters, there won't be any dependent data!\n return Object.assign({}, options);\n }\n\n /**\n * Determine how this request is uniquely identified in cache. Usually this is an exact match for the same key, but it doesn't have to be.\n * The LRU cache implements a `find` method, which means that a cache item can optionally be identified by its node\n * `metadata` (instead of exact key match).\n * This is useful for situations where the user zooms in to a smaller region and wants the original request to\n * count as a cache hit. See subclasses for example.\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {*} This is often a string concatenating unique values for a compound cache key, like `chr_start_end`. If null, it is treated as a cache miss.\n * @public\n */\n _getCacheKey(options) {\n /* istanbul ignore next */\n if (this._enable_cache) {\n throw new Error('Method not implemented');\n }\n return null;\n }\n\n /**\n * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {Promise}\n * @public\n */\n _performRequest(options) {\n /* istanbul ignore next */\n throw new Error('Not implemented');\n }\n\n /**\n * Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.\n * @param {*} response_text The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON.\n * @param {Object} options Request options. These are not typically used when normalizing a response, but the object is available.\n * @returns {*} A list of objects, each object representing one row of data `{column_name: value_for_row}`\n * @public\n */\n _normalizeResponse(response_text, options) {\n return response_text;\n }\n\n /**\n * Perform custom client-side operations on the retrieved data. For example, add calculated fields or\n * perform rapid client-side filtering on cached data. Annotations are applied after cache, which means\n * that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.\n *\n * This result is currently not cached, but it may become so in the future as responsibility for dynamic UI\n * behavior moves to other layers of the application.\n * @param {Object[]} records\n * @param {Object} options\n * @returns {*}\n * @public\n */\n _annotateRecords(records, options) {\n return records;\n }\n\n /**\n * A hook to transform the response after all operations are done. For example, this can be used to prefix fields\n * with a namespace unique to the request, like `log_pvalue` -> `assoc:log_pvalue`. (by applying namespace prefixes to field names last,\n * annotations and validation can happen on the actual API payload, without having to guess what the fields were renamed to).\n * @param records\n * @param options\n * @public\n */\n _postProcessResponse(records, options) {\n return records;\n }\n\n /**\n * All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.\n * @param {object} options Shared options for this request. In LocusZoom, this is typically a copy of `plot.state`.\n * @param {Array[]} dependent_data Zero or more recordsets corresponding to each individual adapter that this one depends on.\n * Can be used to build a request that takes into account prior data.\n * @returns {Promise<*>}\n */\n getData(options = {}, ...dependent_data) {\n // Public facing method to define, perform, and process the request\n options = this._buildRequestOptions(options, ...dependent_data);\n\n const cache_key = this._getCacheKey(options);\n\n // Then retrieval and parse steps: parse + normalize response, annotate\n let result;\n if (this._enable_cache && this._cache.has(cache_key)) {\n result = this._cache.get(cache_key);\n } else {\n // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)\n // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry\n // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,\n // even if the actual cache key wasn't an exact match. (see subclasses for an example; this class is generic)\n result = Promise.resolve(this._performRequest(options))\n // Note: we cache the normalized (parsed) response\n .then((text) => this._normalizeResponse(text, options));\n this._cache.add(cache_key, result, options._cache_meta);\n // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request\n // Otherwise, temporary failures couldn't be resolved by trying again in a moment\n // TODO: In the future, consider providing a way to skip requests (eg, a sentinel value to flag something\n // as not cacheable, like \"no dependent data means no request... but maybe in another place this is used, there will be different dependent data and a request would make sense\")\n result.catch((e) => this._cache.remove(cache_key));\n }\n\n return result\n // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache\n .then((data) => clone(data))\n .then((records) => this._annotateRecords(records, options))\n .then((records) => this._postProcessResponse(records, options));\n }\n}\n\n\n/**\n * Fetch data over the web, usually from a REST API that returns JSON\n * @param {string} config.url The URL to request\n * @extends module:undercomplicate.BaseAdapter\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseUrlAdapter extends BaseAdapter {\n constructor(config = {}) {\n super(config);\n this._url = config.url;\n }\n\n\n /**\n * Default cache key is the URL for this request\n * @public\n */\n _getCacheKey(options) {\n return this._getURL(options);\n }\n\n /**\n * In many cases, the base url should be modified with query parameters based on request options.\n * @param options\n * @returns {*}\n * @private\n */\n _getURL(options) {\n return this._url;\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.\n // (not validating in constructor allows URL adapter to be used as more generic parent class)\n if (!this._url) {\n throw new Error('Web based resources must specify a resource URL as option \"url\"');\n }\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n });\n }\n\n _normalizeResponse(response_text, options) {\n if (typeof response_text === 'string') {\n return JSON.parse(response_text);\n }\n // Some custom usages will return other datatypes. These would need to be handled by custom normalization logic in a subclass.\n return response_text;\n }\n}\n\nexport { BaseAdapter, BaseUrlAdapter };\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value,\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import TRANSFORMS from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n // Two scenarios: we are requesting a field by full name, OR there are transforms to apply\n // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`\n const field_pattern = /^(?:\\w+:\\w+|^\\w+)(?:\\|\\w+)*$/;\n if (!field_pattern.test(field)) {\n throw new Error(`Invalid field specifier: '${field}'`);\n }\n\n const [name, ...transforms] = field.split('|');\n\n this.full_name = field; // fieldname + transforms\n this.field_name = name; // just fieldname\n this.transformations = transforms.map((name) => TRANSFORMS.get(name));\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (data[this.field_name] !== undefined) { // Fallback: value sans transforms\n val = data[this.field_name];\n } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations\n val = extra[this.field_name];\n } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {},\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable,\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * @module\n * @private\n */\nimport {getLinkedData} from './undercomplicate';\n\nimport { DATA_OPS } from '../registry';\n\n\nclass DataOperation {\n /**\n * Perform a data operation (such as a join)\n * @param {String} join_type\n * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!\n * @param params Optional user/layout parameters to be passed to the data function\n */\n constructor(join_type, initiator, params) {\n this._callable = DATA_OPS.get(join_type);\n this._initiator = initiator;\n this._params = params || [];\n }\n\n getData(plot_state, ...dependent_recordsets) {\n // Most operations are joins: they receive two pieces of data (eg left + right)\n // Other ops are possible, like consolidating just one set of records to best value per key\n // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]\n\n // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options\n const context = {plot_state, data_layer: this._initiator};\n return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));\n }\n}\n\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a\n * designated order.\n *\n * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can\n * perform its own calculations, filter results, and apply transforms without influencing other layers.\n * (while still respecting a shared cache where appropriate)\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n /**\n * Parse the data layer configuration when a layer is first created.\n * Validate config, and return entities and dependencies in a format usable for data retrieval.\n * This is used by data layers, and also other data-retrieval functions (like subscribeToDate).\n *\n * Inherent assumptions:\n * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed.\n * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be\n * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the\n * removed adapter.\n * @param {Object} namespace_options\n * @param {Array} data_operations\n * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations,\n * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate\n * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care!\n * @returns {Array} Map of entities and list of dependencies\n */\n config_to_sources(namespace_options = {}, data_operations = [], initiator) {\n const entities = new Map();\n const namespace_local_names = Object.keys(namespace_options);\n\n // 1. Specify how to coordinate data. Precedence:\n // a) EXPLICIT fetch logic,\n // b) IMPLICIT auto-generate fetch order if there is only one NS,\n // c) Throw \"spec required\" error if > 1, because 2 adapters may need to be fetched in a sequence\n let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from}\n if (!dependency_order) {\n dependency_order = { type: 'fetch', from: namespace_local_names };\n data_operations.unshift(dependency_order);\n }\n\n // Validate that all NS items are available to the root requester in DataSources. All layers recognize a\n // default value, eg people copying the examples tend to have defined a datasource called \"assoc\"\n const ns_pattern = /^\\w+$/;\n for (let [local_name, global_name] of Object.entries(namespace_options)) {\n if (!ns_pattern.test(local_name)) {\n throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`);\n }\n\n const source = this._sources.get(global_name);\n if (!source) {\n throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`);\n }\n entities.set(local_name, source);\n\n // Note: Dependency spec checker will consider \"ld(assoc)\" to match a namespace called \"ld\"\n if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) {\n // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join.\n // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch\n // Thus the default behavior is \"fetch all namespaces as though they don't depend on anything.\n // If they depend on something, only then does \"data_ops[@type=fetch].from\" need to be mutated\n dependency_order.from.push(local_name);\n }\n }\n\n let dependencies = Array.from(dependency_order.from);\n\n // Now check all joins. Are namespaces valid? Are they requesting known data?\n for (let config of data_operations) {\n let {type, name, requires, params} = config;\n if (type !== 'fetch') {\n let namecount = 0;\n if (!name) {\n name = config.name = `join${namecount}`;\n namecount += 1;\n }\n\n if (entities.has(name)) {\n throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`);\n }\n requires.forEach((require_name) => {\n if (!entities.has(require_name)) {\n throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`);\n }\n });\n\n const task = new DataOperation(type, initiator, params);\n entities.set(name, task);\n dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB)\n }\n }\n return [entities, dependencies];\n }\n\n /**\n * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end)\n * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts.\n * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances\n * (things that implement a method getData).\n * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order\n * @returns {Promise}\n */\n getData(plot_state, entities, dependencies) {\n if (!dependencies.length) {\n return Promise.resolve([]);\n }\n // The last dependency (usually the last join operation) determines the last thing returned.\n return getLinkedData(plot_state, entities, dependencies, true);\n }\n}\n\n\nexport default Requester;\n\nexport {DataOperation as _JoinTask};\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay,\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import WIDGETS from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n const options = this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n this.addWidget(layout);\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown)\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Add a new widget to the toolbar.\n * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to:\n * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here.\n * - call widget.show() to ensure that the widget is initialized and rendered correctly\n * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default.\n * @param {Object} layout The layout object describing the desired widget\n * @returns {layout.type}\n */\n addWidget(layout) {\n try {\n const widget = WIDGETS.create(layout.type, layout, this);\n this.widgets.push(widget);\n return widget;\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 14,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n const layer_legend = this.parent.data_layers[id].layout.legend;\n if (Array.isArray(layer_legend)) {\n layer_legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape === 'ribbon') {\n // Color ribbons describe a series of color stops: small boxes of color across a continuous\n // scale. Drawn horizontally, or vertically, like:\n // [red | orange | yellow | green ] label\n // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way.\n const width = +element.width || 25;\n const height = +element.height || width;\n const is_horizontal = (element.orientation || 'vertical') === 'horizontal';\n let color_stops = element.color_stops;\n\n const all_elements = selector.append('g');\n const ribbon_group = all_elements.append('g');\n const axis_group = all_elements.append('g');\n let axis_offset = 0;\n if (element.tick_labels) {\n let range;\n if (is_horizontal) {\n range = [0, width * color_stops.length - 1]; // 1 px offset to align tick with inner borders\n } else {\n range = [height * color_stops.length - 1, 0];\n }\n const scale = d3.scaleLinear()\n .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode\n .range(range);\n const axis = (is_horizontal ? d3.axisTop : d3.axisRight)(scale)\n .tickSize(3)\n .tickValues(element.tick_labels)\n .tickFormat((v) => v);\n axis_group\n .call(axis)\n .attr('class', 'lz-axis');\n let bcr = axis_group.node().getBoundingClientRect();\n axis_offset = bcr.height;\n }\n if (is_horizontal) {\n // Shift axis down (so that tick marks aren't above the origin)\n axis_group\n .attr('transform', `translate(0, ${axis_offset})`);\n // Ribbon appears below axis\n ribbon_group\n .attr('transform', `translate(0, ${axis_offset})`);\n } else {\n // Vertical mode: Shift axis ticks to the right of the ribbon\n all_elements.attr('transform', 'translate(5, 0)');\n axis_group\n .attr('transform', `translate(${width}, 0)`);\n }\n\n if (!is_horizontal) {\n // Vertical mode: renders top -> bottom but scale is usually specified low..high\n color_stops = color_stops.slice();\n color_stops.reverse();\n }\n for (let i = 0; i < color_stops.length; i++) {\n const color = color_stops[i];\n const to_next_marking = is_horizontal ? `translate(${width * i}, 0)` : `translate(0, ${height * i})`;\n ribbon_group\n .append('rect')\n .attr('class', element.class || '')\n .attr('stroke', 'black')\n .attr('transform', to_next_marking)\n .attr('stroke-width', 0.5)\n .attr('width', width)\n .attr('height', height)\n .attr('fill', color)\n .call(applyStyles, element.style || {});\n }\n\n // Note: In vertical mode, it's usually easier to put the label above the legend as a separate marker\n // This is because the legend element label is drawn last (can't use it's size to position the ribbon, which is drawn first)\n if (!is_horizontal && element.label) {\n throw new Error('Legend labels not supported for vertical ribbons (use a separate legend item as text instead)');\n }\n // This only makes sense for horizontal labels.\n label_x = (width * color_stops.length + padding);\n label_y += axis_offset;\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset] The distance between the axis title and the axis. Use this to prevent\n * the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly.\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n if (typeof layout.id !== 'string' || !layout.id) {\n throw new Error('Panel layouts must specify \"id\"');\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this._layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this._state_id = this.id;\n this.state[this._state_id] = this.state[this._state_id] || {};\n } else {\n this.state = null;\n this._state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this._data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this._data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this._zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this._event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this._event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this._data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this._data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id]._layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n const target_layer = this.data_layers[id];\n if (!target_layer) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n target_layer.destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (target_layer.svg.container) {\n target_layer.svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(target_layer._layout_idx, 1);\n delete this.state[target_layer._state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id]._layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n const { cliparea } = this.layout;\n\n // Set and position the inner border, style if necessary\n const { margin } = this.layout;\n this.inner_border\n .attr('x', margin.left)\n .attr('y', margin.top)\n .attr('width', this.parent_plot.layout.width - (margin.left + margin.right))\n .attr('height', this.layout.height - (margin.top + margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n const axes_config = this.layout.axes;\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (axes_config.x.range) {\n base_x_range.start = axes_config.x.range.start || base_x_range.start;\n base_x_range.end = axes_config.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: cliparea.height, end: 0 };\n if (axes_config.y1.range) {\n base_y1_range.start = axes_config.y1.range.start || base_y1_range.start;\n base_y1_range.end = axes_config.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: cliparea.height, end: 0 };\n if (axes_config.y2.range) {\n base_y2_range.start = axes_config.y2.range.start || base_y2_range.start;\n base_y2_range.end = axes_config.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n let { _interaction } = this.parent;\n const current_drag = _interaction.dragging;\n if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (_interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = _interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = _interaction.zooming.center - margin.left - this.layout.origin.x;\n const offset_ratio = anchor / cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (current_drag) {\n switch (current_drag.method) {\n case 'background':\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n } else {\n anchor = current_drag.start_x - margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${current_drag.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y;\n ranges[y_shifted][1] = +current_drag.dragged_y;\n } else {\n anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3);\n ranges[y_shifted][0] = cliparea.height;\n ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent._interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n // Redefine b/c might have been changed during call to parent re-render\n _interaction = this.parent._interaction;\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this._zoom_timeout !== null) {\n clearTimeout(this._zoom_timeout);\n }\n this._zoom_timeout = setTimeout(() => {\n this.parent._interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this._initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((id) => {\n const axis = this.layout.axes[id];\n if (!Object.keys(axis).length || axis.render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n axis.render = false;\n } else {\n axis.render = true;\n axis.label = axis.label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n const layout = this.layout;\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n layout.height = Math.max(Math.round(+height), layout.min_height);\n }\n }\n layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0);\n layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', layout.height);\n }\n if (this._initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n const { cliparea, margin } = this.layout;\n if (!isNaN(top) && top >= 0) {\n margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (margin.top + margin.bottom > this.layout.height) {\n extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2);\n margin.top -= extra;\n margin.bottom -= extra;\n }\n if (margin.left + margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2);\n margin.left -= extra;\n margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n margin[m] = Math.max(margin[m], 0);\n });\n cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0);\n cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0);\n cliparea.origin.x = margin.left;\n cliparea.origin.y = margin.top;\n\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this._data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent._panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n const { parent } = this;\n const y_index = this.layout.y_index;\n if (parent._panel_ids_by_y_index[y_index - 1]) {\n parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1];\n parent._panel_ids_by_y_index[y_index - 1] = this.id;\n parent.applyPanelYIndexesToPanelLayouts();\n parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n const { _panel_ids_by_y_index } = this.parent;\n if (_panel_ids_by_y_index[this.layout.y_index + 1]) {\n _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1];\n _panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this._data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this._data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this._data_promises)\n .then(() => {\n this._initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([\\w+_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future.\n * @event data_from_layer\n * @property {object} data\n * @property {String} data.layer The fully qualified ID of the layer emitting this event\n * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum\n * element. It reflects all namespaces and data operations used by that layer.\n * @see event:any_lz_event\n */\n\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this._initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this._panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this._remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this._interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this._event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this._event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this._panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this._panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id]._layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer._layer_state;\n delete this.layout.state[layer._state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n const target_panel = this.panels[id];\n if (!target_panel) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this._panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n target_panel.loader.hide();\n target_panel.toolbar.destroy(true);\n target_panel.curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (target_panel.svg.container) {\n target_panel.svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(target_panel._layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id]._layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the\n * structure of the data, instead of just displaying something.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @listens event:data_from_layer\n * @param {Object} [opts] Options\n * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched.\n * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details.\n * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested,\n * this is usually required in order to specify dependency order and join operations. See data layer documentation for details.\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(opts, success_callback) {\n const { from_layer, namespace, data_operations, onerror } = opts;\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = onerror || function (err) {\n console.error('An error occurred while acting on an external callback', err);\n };\n\n if (from_layer) {\n // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places.\n const base_prefix = `${this.getBaseId()}.`;\n // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code.\n const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`;\n\n // Ensure that a valid layer exists to watch\n let is_valid_layer = false;\n for (let p of Object.values(this.panels)) {\n is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target);\n if (is_valid_layer) {\n break;\n }\n }\n if (!is_valid_layer) {\n throw new Error(`Could not subscribe to unknown data layer ${layer_target}`);\n }\n\n const listener = (eventData) => {\n if (eventData.data.layer !== layer_target) {\n // Same event name fires for many layers; only fire success cb for the one layer we want\n return;\n }\n try {\n success_callback(eventData.data.content, this);\n } catch (error) {\n error_callback(error);\n }\n };\n\n this.on('data_from_layer', listener);\n return listener;\n }\n\n // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of\n // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized\n // in connection to some other visualization)\n if (!namespace) {\n throw new Error(\"subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option\");\n }\n\n const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot.\n const listener = () => {\n try {\n // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely,\n // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame.\n this.lzd.getData(this.state, entities, dependencies)\n .then((new_data) => success_callback(new_data, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this._remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this._remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this._remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this._initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /**\n * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another,\n * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties,\n * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.\n * @public\n */\n mutateLayout() {\n Object.values(this.panels).forEach((panel) => {\n Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout());\n });\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n const { _interaction } = this;\n if (panel_id) {\n return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(_interaction.dragging || _interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this._panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this._initialized) {\n this._panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let panel of Object.values(this.panels)) {\n if (panel.layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_layout = panel.layout;\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel_layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel_layout.margin.right, 0);\n panel_layout.width += delta;\n panel_layout.margin.left = x_linked_margins.left;\n panel_layout.margin.right = x_linked_margins.right;\n panel_layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height,\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n const resize_listener = () => window.requestAnimationFrame(() => this.rescaleSVG());\n\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener\n // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed\n if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers\n const options = { root: document.documentElement, threshold: 0.9 };\n const observer = new IntersectionObserver((entries, observer) => {\n if (entries.some((entry) => entry.intersectionRatio > 0)) {\n this.rescaleSVG();\n }\n }, options);\n // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup\n observer.observe(this.container);\n }\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this._mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this._panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent._panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent._panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this._panel_boundaries.hide_timeout);\n this._panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this._panel_boundaries.hide_timeout = setTimeout(() => {\n this._panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this._mouse_guide.vertical.attr('x', -1);\n this._mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this._mouse_guide.vertical.attr('x', coords[0]);\n this._mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n const { _interaction } = this;\n if (_interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x;\n _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y;\n this.panels[_interaction.panel_id].render();\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this._initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this._interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n const { _interaction } = this;\n if (!_interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[_interaction.panel_id] != 'object') {\n this._interaction = {};\n return this;\n }\n const panel = this.panels[_interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel._data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (_interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (_interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (_interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(_interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this._interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} value value\n */\nconst if_value = (parameters, value) => {\n if (typeof value == 'undefined' || parameters.field_value !== value) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} value value\n * @returns {*}\n */\nconst numerical_bin = (parameters, value) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+value < prev || (+value >= prev && +value < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @function ordinal_cycle\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n *\n * @function stable_choice\n *\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, value) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return nullval;\n }\n if (+value <= parameters.breaks[0]) {\n return values[0];\n } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +value && breaks[idx] >= +value) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\n/**\n * Calculate the effect direction based on beta, or the combination of beta and standard error.\n * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.\n *\n * @function effect_direction\n * @param parameters\n * @param parameters.'+' The value to return if the effect direction is positive\n * @param parameters.'-' The value to return if the effect direction is positive\n * @param parameters.beta_field The name of the field containing beta\n * @param parameters.stderr_beta_field The name of the field containing stderr_beta\n * @param {Object} input This function should receive the entire datum object, rather than one single field\n * @returns {null}\n */\nfunction effect_direction(parameters, input) {\n if (input === undefined) {\n return null;\n }\n\n const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters;\n\n if (!beta_field || !stderr_beta_field) {\n throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`);\n }\n\n const beta_val = input[beta_field];\n const se_val = input[stderr_beta_field];\n\n if (beta_val !== undefined) {\n if (se_val !== undefined) {\n if ((beta_val - 1.96 * se_val) > 0) {\n return plus_result;\n } else if ((beta_val + 1.96 * se_val) < 0) {\n return neg_result || null;\n }\n } else {\n if (beta_val > 0) {\n return plus_result;\n } else if (beta_val < 0) {\n return neg_result;\n }\n }\n }\n // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid\n // about expected data formats for layouts.\n return null;\n}\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, findFields, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n/**\n * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.\n * @property {module:LocusZoom_DataFunctions|'fetch'} type\n * @property {String[]} [from] For operations of type \"fetch\", this is required. By default, it will fill in any items provided in \"namespace\" (everything specified in namespace triggers an adapter/network request)\n * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace).\n * Eg, for ld to be fetched after association data, specify \"ld(assoc)\". Most LocusZoom examples fill in all items, in order to make the examples more clear.\n * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation.\n * Eg, if the retrieved data is combined via several left joins in series: `name: \"assoc_plus_ld\"` -> feeds into `{name: \"final\", requires: [\"assoc_plus_ld\"]}`\n * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just\n * the names of other namespaces (or data operations) whose results must be available before a join can be performed\n * @property {String[]} params Any user-defined parameters that should be passed to the particular join function\n * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']`\n * Separate from this section, data functions will also receive a copy of \"plot.state\" automatically.\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or \"line\" for a path.\n * A special \"ribbon\" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD)\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {'vertical'|'horizontal'} [orientation='vertical'] For shape \"ribbon\", specifies whether to draw the ribbon vertically or horizontally.\n * @property {Array} [tick_labels] For shape \"ribbon\", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops.\n * @property {String[]} [color_stops] For shape \"ribbon\", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels.\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n namespace: {},\n data_operations: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is\n * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely\n * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is\n * your job to assure that all of the expected fields are present in every element)\n * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data (\"assoc\")\n * to the globally unique name for something defined in LocusZoom.DataSources.\n * Namespaces allow a single layout to be reused to plot many tracks of the same type: \"given some form of association data, plot it\".\n * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse\n * a layout with a new provider of data- like plotting two association studies stacked together-\n * only the namespace section of the layout needs to be overridden.\n * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})`\n * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions})\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this._layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this._state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this._layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this._tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this._global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n\n // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval\n this._data_contract = new Set(); // List of all fields requested by the layout\n this._entities = new Map();\n this._dependencies = [];\n this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout\n }\n\n /****** Public interface: methods for manipulating the layer from other parts of LZ */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index + 1]) {\n layer_order[current_index] = layer_order[current_index + 1];\n layer_order[current_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index - 1]) {\n layer_order[current_index] = layer_order[current_index - 1];\n layer_order[current_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this._layer_state.extra_fields[id]) {\n this._layer_state.extra_fields[id] = {};\n }\n this._layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /**\n * A list of operations that should be run when the layout is mutated\n * Typically, these are things done once when a layout is first specified, that would not automatically\n * update when the layout was changed.\n * @public\n */\n mutateLayout() {\n // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract.\n if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester\n const { namespace, data_operations } = this.layout;\n this._data_contract = findFields(this.layout, Object.keys(namespace));\n const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this);\n this._entities = entities;\n this._dependencies = dependencies;\n }\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n *\n * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that\n * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data),\n * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}`\n * @protected\n * @param {Object} element The data associated with a particular element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression\n const id_field = this.layout.id_field;\n let value = element[id_field];\n if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) {\n // No field value was found directly, but if it looks like a template expression, next, try parsing that\n // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot!\n value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient\n }\n if (value === null || value === undefined) {\n // Neither exact field nor template options produced an ID\n throw new Error('Unable to generate element ID');\n }\n const element_id = value.toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Abstract method. It should be overridden by data layers that implement separate status\n * nodes, such as genes or intervals.\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or\n * a group of unrelated intervals (all markings grouped within a category).\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n\n // Does the data from the API satisfy the list of fields expected by this layout?\n // Not every record will have every possible field (example: left joins like assoc + ld). The check is \"did\n // we see this field at least once in any record at all\".\n if (this.data.length && this._data_contract.size) {\n const fields_unseen = new Set(this._data_contract);\n for (let record of this.data) {\n Object.keys(record).forEach((field) => fields_unseen.delete(field));\n if (!fields_unseen.size) {\n // Once every requested field has been seen in at least one record, no need to look at more records\n break;\n }\n }\n if (fields_unseen.size) {\n // Current implementation is a soft warning, so that certain \"incremental enhancement\" features\n // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info.\n // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data.\n console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in \"data_operations\" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`);\n }\n }\n\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value);\n }\n\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this._layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const _layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = _layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this._state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this._state_id] = _layer_state;\n }\n this._layer_state = _layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this._tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this._tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this._layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this._tooltips[id].selector.html('');\n this._tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this._tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this._tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this._tooltips[id]) {\n if (typeof this._tooltips[id].selector == 'object') {\n this._tooltips[id].selector.remove();\n }\n delete this._tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this._layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this._tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this._tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this._tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this._tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n const tooltip_layout = this.layout.tooltip;\n if (typeof tooltip_layout != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof tooltip_layout.show == 'string') {\n show_directive = { and: [ tooltip_layout.show ] };\n } else if (typeof tooltip_layout.show == 'object') {\n show_directive = tooltip_layout.show;\n }\n\n let hide_directive = {};\n if (typeof tooltip_layout.hide == 'string') {\n hide_directive = { and: [ tooltip_layout.hide ] };\n } else if (typeof tooltip_layout.hide == 'object') {\n hide_directive = tooltip_layout.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const _layer_state = this._layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (_layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this._layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this._layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true,\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this._layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this._layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self._state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies)\n .then((new_data) => {\n this.data = new_data;\n this.applyDataMethods();\n this._initialized = true;\n // Allow listeners (like subscribeToData) to see the information associated with a layer\n this.parent.emit(\n 'data_from_layer',\n { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin\n true,\n );\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 15,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n // FIXME: Make gene text font sizes scalable\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size,\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n tooltip: null,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this._global_statuses).forEach((global_status) => {\n if (this._global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n data_layer._label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this._label_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer._label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer._label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer._label_texts.nodes();\n data_layer._label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this._label_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this._label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this._label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this._label_texts) {\n this._label_texts.remove();\n }\n\n this._label_texts = this._label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this._label_lines) {\n this._label_lines.remove();\n }\n this._label_lines = this._label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this._label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this._label_texts) {\n this._label_texts.remove();\n }\n if (this._label_lines) {\n this._label_lines.remove();\n }\n if (this._label_groups) {\n this._label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this._label_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{assoc:variant|htmlescape}}
                                                                                                                                                                                                                                                                                \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
                                                                                                                                                                                                                                                                                \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
                                                                                                                                                                                                                                                                                \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
                                                                                                                                                                                                                                                                                `,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

                                                                                                                                                                                                                                                                                {{gene_name|htmlescape}}

                                                                                                                                                                                                                                                                                '\n + 'Gene ID: {{gene_id|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + 'Transcript ID: {{transcript_id|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
                                                                                                                                                                                                                                                                                ConstraintExpected variantsObserved variantsConst. Metric
                                                                                                                                                                                                                                                                                Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
                                                                                                                                                                                                                                                                                o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
                                                                                                                                                                                                                                                                                Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
                                                                                                                                                                                                                                                                                o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
                                                                                                                                                                                                                                                                                pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
                                                                                                                                                                                                                                                                                o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

                                                                                                                                                                                                                                                                                {{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{catalog:variant|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + 'Top Trait: {{catalog:trait|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + 'Top P Value: {{catalog:log_pvalue|logtoscinotation}}
                                                                                                                                                                                                                                                                                '\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
                                                                                                                                                                                                                                                                                ' +\n '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
                                                                                                                                                                                                                                                                                ' +\n 'Promoter
                                                                                                                                                                                                                                                                                ' +\n '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
                                                                                                                                                                                                                                                                                ' +\n '{{#if access:target}}Target: {{access:target|htmlescape}}
                                                                                                                                                                                                                                                                                {{/if}}' +\n 'Score: {{access:score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n id: 'recombrate',\n namespace: { 'recomb': 'recomb' },\n data_operations: [\n { type: 'fetch', from: ['recomb'] },\n ],\n type: 'line',\n tag: 'recombination',\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: 'recomb:position',\n },\n y_axis: {\n axis: 2,\n field: 'recomb:recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)'],\n },\n {\n type: 'left_match',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'ld'],\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n id_field: 'assoc:variant',\n coalesce: {\n active: true,\n },\n point_shape: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 'diamond',\n },\n },\n {\n // Not every dataset will provide these params\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'assoc:beta',\n stderr_beta_field: 'assoc:se',\n },\n },\n 'circle',\n ],\n point_size: {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: 'ld:correlation',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8'\n {\n shape: 'ribbon',\n orientation: 'vertical',\n width: 10,\n height: 15,\n color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0],\n },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: 'assoc:position',\n },\n y_axis: {\n axis: 1,\n field: 'assoc:log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n namespace: { 'access': 'access' },\n data_operations: [\n { type: 'fetch', from: ['access'] },\n ],\n match: { send: 'access:target', receive: 'access:target' },\n // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.\n id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',\n filters: [\n { field: 'access:score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: 'access:start1',\n field2: 'access:start2',\n },\n y_axis: {\n axis: 1,\n field: 'access:score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n\n base.data_operations.push({\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_catalog',\n requires: ['assoc_plus_ld', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n });\n\n base.tooltip.html += '{{#if catalog:rsid}}
                                                                                                                                                                                                                                                                                See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n return base;\n}();\n\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n namespace: { 'phewas': 'phewas' },\n data_operations: [\n { type: 'fetch', from: ['phewas'] },\n ],\n point_shape: [\n {\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'phewas:beta',\n stderr_beta_field: 'phewas:se',\n },\n },\n 'circle',\n ],\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',\n x_axis: {\n field: 'lz_auto_x', // Automatically added by the category_scatter layer\n category_field: 'phewas:trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: 'phewas:log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: 'phewas:trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Trait: {{phewas:trait_label|htmlescape}}
                                                                                                                                                                                                                                                                                \nTrait Category: {{phewas:trait_group|htmlescape}}
                                                                                                                                                                                                                                                                                \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
                                                                                                                                                                                                                                                                                β: {{phewas:beta|scinotation|htmlescape}}
                                                                                                                                                                                                                                                                                {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}`,\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{phewas:trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: 'phewas:log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n data_operations: [\n {\n type: 'fetch',\n from: ['gene', 'constraint(gene)'],\n },\n {\n name: 'gene_constraint',\n type: 'genes_to_gnomad_constraint',\n requires: ['gene', 'constraint'],\n },\n ],\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n data_operations: [\n {\n type: 'fetch', from: ['assoc', 'catalog'],\n },\n {\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n },\n ],\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: '#0000CC',\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'catalog:rsid', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n },\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 300,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 46,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 75, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 40,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{catalog:trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: 'catalog:trait', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: 'ld:correlation', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '12px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 55, bottom: 20, left: 70 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu),\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 55, bottom: 120, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 55, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel),\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or applying namespaces.\n let base = super.get(type).get(name);\n\n // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.\n // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)\n const custom_namespaces = overrides.namespace;\n if (!base.namespace) {\n // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout\n // NOTE: The \"merge namespace\" behavior means that data layers can add new data easily, but this method\n // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).\n delete overrides.namespace;\n }\n let result = merge(overrides, base);\n\n if (custom_namespaces) {\n result = applyNamespaces(result, custom_namespaces);\n }\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n\n // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested\n // from external sources. This is purely a hint, because not every layout is generated through the registry.\n if (type === 'data_layer' && copy.namespace) {\n copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();\n }\n\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","/**\n * \"Data operation\" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results\n *\n * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation\n * is a \"join\", such as combining association + LD together into a single set of records for plotting. Several join\n * functions (that operate by analogy to SQL) are provided built-in.\n *\n * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):\n * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.\n * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,\n * this is the recommended path to do so)\n * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),\n * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,\n * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks\n * (PheWAS), automatic panel legends or color schemes (BED tracks), etc.\n *\n * Usually, a data operation receives two recordsets (the left and right members of the join, like \"assoc\" and \"ld\").\n * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network\n * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is\n * uncommon. (if possible, try to provide your data with fewer adapters/network requests!)\n *\n * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,\n * particularly for advanced features, may carry assumptions about field names/ formatting.\n * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,\n * or it may match two datasets based on a specific way of identifying the variant)\n *\n * @module LocusZoom_DataFunctions\n */\nimport {joins} from '../data/undercomplicate';\n\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"data join\" functions.\n * @alias module:LocusZoom~DataFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\nfunction _wrap_join(handle) {\n // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).\n\n // Many of our join functions are implemented with a different number of arguments than what a datafunction\n // actually receives. (eg, a join function is generic and doesn't care about \"context\" information like plot.state)\n // This wrapper is simple shared code to handle required validation and conversion stuff.\n return (context, deps, ...params) => {\n if (deps.length !== 2) {\n throw new Error('Join functions must receive exactly two recordsets');\n }\n return handle(...deps, ...params);\n };\n}\n\n// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to\n// pick the most significant claim in the catalog for a variant, rather than joining every possible match.\n// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.\nfunction assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {\n if (!assoc_data.length) {\n return assoc_data;\n }\n\n // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each\n const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);\n\n const catalog_flat = []; // Store only the top significant claim for each catalog variant entry\n for (let claims of catalog_by_variant.values()) {\n // Find max item within this set of claims, push that to catalog_\n let best = 0;\n let best_variant;\n for (let item of claims) {\n const val = item[catalog_logp_name];\n if ( val >= best) {\n best_variant = item;\n best = val;\n }\n }\n best_variant.n_catalog_matches = claims.length;\n catalog_flat.push(best_variant);\n }\n return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);\n}\n\n// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.\nfunction genes_to_gnomad_constraint(genes_data, constraint_data) {\n genes_data.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return genes_data;\n}\n\n\n/**\n * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset\n *\n * @function\n * @name left_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('left_match', _wrap_join(joins.left_match));\n\n/**\n * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.\n *\n * @function\n * @name inner_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('inner_match', _wrap_join(joins.inner_match));\n\n/**\n * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.\n *\n * @function\n * @name full_outer_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('full_outer_match', _wrap_join(joins.full_outer_match));\n\n/**\n * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.\n *\n * @function\n * @name assoc_to_gwas_catalog\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: assoc records, then catalog records\n * @param {String} assoc_key The name of the key field in association data, eg variant ID\n * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID\n * @param {String} catalog_log_p_name The name of the \"log_pvalue\" field in gwas catalog data, used to choose the most significant claim for a given variant\n */\nregistry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));\n\n/**\n * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).\n *\n * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.\n *\n * @function\n * @name genes_to_gnomad_constraint\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data\n */\nregistry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));\n\nexport default registry;\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n DATA_OPS as DataFunctions,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n WIDGETS as Widgets,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n DataFunctions,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/./node_modules/@hapi/hoek/lib/assert.js","webpack://[name]/./node_modules/@hapi/hoek/lib/error.js","webpack://[name]/./node_modules/@hapi/hoek/lib/stringify.js","webpack://[name]/./node_modules/@hapi/topo/lib/index.js","webpack://[name]/./node_modules/just-clone/index.js","webpack://[name]/webpack/bootstrap","webpack://[name]/webpack/runtime/compat get default export","webpack://[name]/webpack/runtime/define property getters","webpack://[name]/webpack/runtime/hasOwnProperty shorthand","webpack://[name]/webpack/runtime/make namespace object","webpack://[name]/./esm/version.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./esm/data/undercomplicate/lru_cache.js","webpack://[name]/./esm/data/undercomplicate/util.js","webpack://[name]/./esm/data/undercomplicate/requests.js","webpack://[name]/./esm/data/undercomplicate/joins.js","webpack://[name]/./esm/helpers/parse.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/./esm/data/undercomplicate/adapter.js","webpack://[name]/./esm/registry/adapters.js","webpack://[name]/external \"d3\"","webpack://[name]/./esm/components/constants.js","webpack://[name]/./esm/helpers/transforms.js","webpack://[name]/./esm/registry/transforms.js","webpack://[name]/./esm/data/field.js","webpack://[name]/./esm/helpers/jsonpath.js","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/data/requester.js","webpack://[name]/./esm/helpers/common.js","webpack://[name]/./esm/components/toolbar/widgets.js","webpack://[name]/./esm/registry/widgets.js","webpack://[name]/./esm/components/toolbar/index.js","webpack://[name]/./esm/components/legend.js","webpack://[name]/./esm/components/panel.js","webpack://[name]/./esm/helpers/display.js","webpack://[name]/./esm/components/plot.js","webpack://[name]/./esm/registry/matchers.js","webpack://[name]/./esm/helpers/scalable.js","webpack://[name]/./esm/registry/scalable.js","webpack://[name]/./esm/components/data_layer/base.js","webpack://[name]/./esm/components/data_layer/annotation_track.js","webpack://[name]/./esm/components/data_layer/highlight_regions.js","webpack://[name]/./esm/components/data_layer/arcs.js","webpack://[name]/./esm/components/data_layer/genes.js","webpack://[name]/./esm/components/data_layer/line.js","webpack://[name]/./esm/components/data_layer/scatter.js","webpack://[name]/./esm/helpers/render.js","webpack://[name]/./esm/registry/data_layers.js","webpack://[name]/./esm/layouts/index.js","webpack://[name]/./esm/registry/layouts.js","webpack://[name]/./esm/registry/data_ops.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/index.js"],"names":["AssertError","module","exports","condition","args","length","Error","Stringify","super","filter","arg","map","message","join","captureStackTrace","this","assert","JSON","stringify","apply","err","Assert","internals","_items","nodes","options","before","concat","after","group","sort","includes","Array","isArray","node","item","seq","push","manual","valid","_sort","others","other","Object","assign","mergeSort","i","graph","graphAfters","create","groups","expandedGroups","graphNodeItem","ancestors","children","child","visited","sorted","next","j","shouldSeeCount","seenCount","k","seqIndex","value","sortedItem","a","b","getRegExpFlags","regExp","source","flags","global","ignoreCase","multiline","sticky","unicode","clone","obj","result","key","type","toString","call","slice","Date","getTime","RegExp","__webpack_module_cache__","__webpack_require__","moduleId","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","prop","prototype","hasOwnProperty","r","Symbol","toStringTag","RegistryBase","Map","name","has","override","set","delete","from","keys","ClassRegistry","parent_name","source_name","overrides","console","warn","arguments","base","sub","add","LLNode","metadata","prev","LRUCache","max_size","_max_size","_cur_size","_store","_head","_tail","cached","prior","_remove","old","match_callback","data","getLinkedData","shared_options","entities","dependencies","consolidate","parsed","spec","exec","name_alone","name_deps","deps","split","_parse_declaration","dag","toposort","entries","e","order","responses","provider","depends_on","this_result","Promise","all","then","prior_results","_provider_name","getData","values","all_results","groupBy","records","group_key","item_group","_any_match","left","right","left_key","right_key","right_index","results","left_match_value","right_matches","right_item","left_index","right_match_value","left_match","REGEX_MARKER","parseMarker","test","match","BaseApiAdapter","BaseLZAdapter","config","_config","cache_enabled","cache_size","_enable_cache","_cache","dependent_data","response_text","_buildRequestOptions","cache_key","_getCacheKey","resolve","_performRequest","text","_normalizeResponse","_cache_meta","catch","remove","_annotateRecords","_postProcessResponse","_url","url","_getURL","fetch","response","ok","statusText","parse","params","prefix_namespace","limit_fields","_prefix_namespace","_limit_fields","Set","chr","start","end","superset","find","md","row","reduce","acc","label","a_record","fieldname","suffixer","BaseUMAdapter","_genome_build","genome_build","build","constructor","N","every","fields","record","AssociationLZ","_source_id","request_options","GwasCatalogLZ","_validateBuildSource","source_query","GeneLZ","GeneConstraintLZ","state","genes_data","unique_gene_names","gene","gene_name","query","replace","body","method","headers","LDServer","assoc_data","assoc_variant_name","_findPrefixedKey","assoc_logp_name","refvar","best_hit","ldrefvar","best_logp","variant","log_pvalue","lz_is_ld_refvar","chrom","pos","ref","alt","coord","String","__find_ld_refvar","_skip_request","ld_refvar","ld_source","ld_population","ld_pop","population","encodeURIComponent","combined","chainRequests","payload","forEach","RecombLZ","StaticSource","_data","PheWASLZ","registry","d3","STATUSES","verbs","adjectives","log10","isNaN","Math","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","s","is_numeric","urlencode","template_string","funcs","substring","func","_collectTransforms","Field","field","transforms","full_name","field_name","transformations","val","transform","extra","undefined","_applyTransformations","ATTR_REGEX","EXPR_REGEX","get_next_token","q","substr","attr","depth","m","attrs","get_item_at_deep_path","path","parent","tokens_to_keys","selectors","sel","remaining_selectors","paths","p","_","__","subject","uniqPaths","arr","elem","localeCompare","_query","matches","items","get_items_from_tokens","normalize_query","selector","tokenize","sqrt3","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","layout","shared_namespaces","requested_ns","merge","custom_layout","default_layout","property","custom_type","default_type","deepCopy","nameToSymbol","shape","factory_name","charAt","toUpperCase","findFields","prefixes","field_finder","all_ns","value_type","a_match","renameField","old_name","new_name","warn_transforms","this_type","escaped","filter_regex","match_val","regex","mutate_attrs","value_or_callable","value_or_callback","old_value","new_value","mutate","query_attrs","DataOperation","join_type","initiator","_callable","_initiator","_params","plot_state","dependent_recordsets","data_layer","sources","_sources","namespace_options","data_operations","namespace_local_names","dependency_order","unshift","ns_pattern","local_name","global_name","dep_spec","requires","namecount","require_name","task","generateCurtain","showing","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","parentNode","insert","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","height","_total_height","style","x","width","delay","setTimeout","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","BaseWidget","color","parent_panel","parent_svg","button","persist","position","group_position","initialize","status","menu","shouldPersist","force","destroy","Button","parent_toolbar","tag","title","permanent","outer_selector","inner_selector","scroll_position","hidden","getBaseId","scrollTop","populate","page_scroll_top","document","documentElement","container_offset","getContainerOffset","toolbar_client_rect","button_client_rect","menu_client_rect","total_content_height","scrollHeight","top","bottom","base_max_width","container_max_width","content_max_width","base_max_height","setPopulate","menu_populate_function","setOnclick","highlight","bool","Boolean","setStatus","onmouseover","onmouseout","onclick","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","RegionScale","positionIntToString","class","FilterField","_data_layer","data_layers","layer_name","_event_name","custom_event_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","index","indexOf","_getTarget","splice","_clearFilter","emit","filter_id","Number","input_size","timer","debounce","_getValue","_setFilter","render","DownloadSVG","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","rule","selectorText","cssText","element","styleElement","createElement","setAttribute","innerHTML","refNode","hasChildNodes","insertBefore","rescale","copy","cloneNode","selectAll","each","dy","serializer","XMLSerializer","_getDimensions","_appendCSS","_getCSS","serializeToString","_generateSVG","markup","blob","Blob","createObjectURL","DownloadPNG","svg_url","canvas","getContext","reject","image","Image","onload","drawImage","toBlob","png","src","RemovePanel","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","_panel_ids_by_y_index","moveDown","ShiftRegion","step","applyState","ZoomRegion","can_zoom","current_region_scale","max_region_scale","min_region_scale","new_region_scale","delta","Menu","menu_html","ResizeToData","scaleHeightToData","ToggleLegend","legend","DisplayOptions","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","radioId","has_option","choice","defaultName","default_config_display_name","display","SetState","state_field","show_selected","new_state","choice_name","choice_value","Toolbar","widgets","hide_timeout","addWidget","widget","error","_panel_boundaries","dragging","_interaction","orientation","origin","padding","label_size","Legend","background_rect","elements","elements_group","line_height","_data_layer_ids_by_z_index","reverse","layer_legend","label_x","label_y","shape_factory","path_y","is_horizontal","color_stops","all_elements","ribbon_group","axis_group","axis_offset","tick_labels","range","scale","domain","axis","tickSize","tickValues","tickFormat","v","to_next_marking","radius","PI","bcr","right_x","pad_from_bottom","pad_from_right","min_height","margin","background_click","cliparea","axes","y1","y2","interaction","drag_background_to_pan","drag_x_ticks_to_scale","drag_y1_ticks_to_scale","drag_y2_ticks_to_scale","scroll_to_zoom","x_linked","y1_linked","y2_linked","show_loading_indicator","Panel","panels","_initialized","_layout_idx","_state_id","_data_promises","x_scale","y1_scale","y2_scale","x_extent","y1_extent","y2_extent","x_ticks","y1_ticks","y2_ticks","_zoom_timeout","_event_hooks","initializeLayout","event","hook","theseHooks","hookMatch","eventData","bubble","eventContext","sourceID","target","hookToRun","parseFloat","y_axis","z_index","dlid","idx","layout_idx","data_layer_layout","target_layer","destroyAllTooltips","container","applyDataLayerZIndexesToDataLayerLayouts","setAllElementStatus","clipRect","inner_border","generateExtents","constrain","limit_exponent","neg_min","neg_max","pos_min","pos_max","Infinity","ranges","axes_config","base_x_range","x_shifted","base_y1_range","y1_shifted","base_y2_range","y2_shifted","current_drag","panel_id","linked_panel_ids","anchor","scalar","zooming","current_extent_size","current_scaled_extent_size","round","invert","zoom_factor","potential_extent_size","new_extent_size","center","offset_ratio","new_x_extent_start","dragged_x","start_x","y_shifted","dragged_y","start_y","renderAxis","zoom_handler","_canInteract","coords","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","setDimensions","setOrigin","setMargin","x_range","y1_range","y2_range","addDataLayer","base_id","clipPath","addBasicLoader","clearSelections","x_axis","x_axis_label","y1_axis","y1_axis_label","y2_axis","y2_axis_label","namespace","mousedown","startDrag","select","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","decoupled","getAxisExtent","extent","ticks","baseTickConfig","self","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","shrink_sml","high_u_bias","u5_bias","c","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tick_format","t","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","panel_boundaries","mouse_guide","Plot","datasource","_remap_promises","_base_layout","lzd","_external_listeners","these_hooks","anyEventData","event_name","panel_layout","panelId","mode","panelsList","pid","layer","_layer_state","_setDefaultState","target_panel","clearPanelData","opts","success_callback","from_layer","onerror","error_callback","base_prefix","layer_target","startsWith","is_valid_layer","some","listener","config_to_sources","new_data","state_changes","mods","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","mutateLayout","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","clientRect","addPanel","height_scaling_factor","panel_width","panel_height","final_height","x_linked_margins","resize_listener","window","requestAnimationFrame","rescaleSVG","addEventListener","trackExternalListener","IntersectionObserver","threshold","observer","entry","intersectionRatio","observe","load_listener","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","_mouse_guide","vertical","horizontal","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","emitted_by","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","ret","positionStringToInt","suffixre","mult","tokens","variable","branch","close","astify","token","shift","dest","else","ast","cache","render_node","item_value","target_value","if_value","parameters","field_value","numerical_bin","breaks","null_value","curr","categorical_bin","categories","ordinal_cycle","stable_choice","max_cache_size","clear","hash","charCodeAt","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","effect_direction","input","beta_field","stderr_beta_field","plus_result","neg_result","beta_val","se_val","id_field","tooltip","tooltip_positioning","behaviors","BaseDataLayer","_base_id","_filter_func","_tooltips","_global_statuses","_data_contract","_entities","_dependencies","layer_order","current_index","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","fields_unseen","debug","lz_is_match","getDataLayer","getPanel","getPlot","applyCustomDataMethods","option_layout","element_data","data_index","resolveScalableParameter","scale_function","f","getElementAnnotation","dimension","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","plot_layout","layer_layout","tooltip_box","data_layer_height","data_layer_width","x_center","y_center","tooltip_top","tooltip_left","arrow_type","arrow_top","arrow_left","placement","arrow_size","offset_right","offset_left","arrow","filter_rules","array","is_match","test_func","bind","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","_getTooltipPosition","_drawTooltip","first_time","tooltip_layout","resolveStatus","statuses","directive","previousValue","currentValue","sub_status","sub_operator","show_directive","and","hide_directive","antistatus","show_resolved","hide_resolved","has_tooltip","createTooltip","exclusive","get_element_id_error","element_status_node_id","getElementStatusNodeId","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","event_match","executeBehaviors","requiredKeyStates","datum","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","AnnotationTrack","_hitareas_group","_visible_lines_group","track_data","_applyFilters","hit_areas_selection","_getX","x_left","left_node","left_node_x_center","enter","crds","exit","applyBehaviors","fill_opacity","regions","start_field","end_field","merge_field","HighlightRegions","cur_item","prev_item","new_start","new_end","_mergeNodes","fill","Arcs","_make_line","x1","field1","x2","field2","xmid","curve","line","hitareas","stroke","label_font_size","label_exon_spacing","exon_height","bounding_box_padding","track_vertical_spacing","Genes","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","font_size","temp_text","label_width","getBBox","gene_id","gene_version","transcript_id","transcripts","display_range","text_anchor","centered_margin","display_domain","track","potential_track","collision_on_potential_track","placed_gene","min_start","exons","assignTracks","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","Line","x_field","y_field","y0","path_class","global_status","default_orthogonal_layout","OrthogonalLine","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","Scatter","spacing","handle_lines","lines","min_x","max_x","flip","dn","dnl","dnx","text_swing","dnlx2","line_swing","_label_texts","da","dal","_label_lines","dax","abound","bbound","_label_iterations","again","db","adjust","new_a_y","new_b_y","min_y","max_y","label_elements","separate_labels","xcs","ycs","final_data","x_start","y_start","current_group","_combine","_start_run","in_combine_region","coalesce_scatter_points","label_data","_label_groups","style_class","groups_enter","flip_labels","item_data","CategoryScatter","_categories","xField","category_field","sourceData","ak","bk","av","toLowerCase","bv","uniqueCategories","category","bounds","categoryNames","_setDynamicColorScheme","from_source","color_params","colorParams","_getColorScale","baseParams","parameters_categories_hash","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","LZ_SIG_THRESHOLD_LOGP","standard_association_tooltip","or","standard_association_tooltip_with_label","standard_genes_tooltip","catalog_variant_tooltip","coaccessibility_tooltip","significance_layer","recomb_rate_layer","association_pvalues_layer","coaccessibility_layer","association_pvalues_catalog_layer","catalog","phewas_pvalues_layer","genes_layer","genes_layer_filtered","annotation_catalog_layer","ldlz2_pop_selector_menu","gene_selector_menu","standard_panel_toolbar","standard_plot_toolbar","standard_association_toolbar","region_nav_plot_toolbar","association_panel","coaccessibility_panel","association_catalog_panel","genes_panel","phewas_panel","annotation_catalog_panel","standard_association_plot","association_catalog_plot","standard_phewas_plot","coaccessibility_plot","color_config","standard_association","standard_association_with_label","standard_genes","catalog_variant","coaccessibility","toolbar_widgets","ldlz2_pop_selector","standard_panel","standard_plot","region_nav_plot","significance","recomb_rate","association_pvalues","association_pvalues_catalog","phewas_pvalues","genes","genes_filtered","annotation_catalog","association","association_catalog","phewas","plot","standard_phewas","custom_namespaces","_auto_fields","contents","list","_wrap_join","handle","catalog_data","assoc_key","catalog_key","catalog_logp_name","catalog_by_variant","catalog_flat","claims","best_variant","best","n_catalog_matches","constraint_data","alias","constraint","LocusZoom","version","iterator","dataset","region","parsed_state","chrpos","parsePositionQuery","refresh","DataSources","_registry","source_id","Adapters","DataLayers","DataFunctions","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","install"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":["'use strict';\n\nconst AssertError = require('./error');\n\nconst internals = {};\n\n\nmodule.exports = function (condition, ...args) {\n\n if (condition) {\n return;\n }\n\n if (args.length === 1 &&\n args[0] instanceof Error) {\n\n throw args[0];\n }\n\n throw new AssertError(args);\n};\n","'use strict';\n\nconst Stringify = require('./stringify');\n\n\nconst internals = {};\n\n\nmodule.exports = class extends Error {\n\n constructor(args) {\n\n const msgs = args\n .filter((arg) => arg !== '')\n .map((arg) => {\n\n return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);\n });\n\n super(msgs.join(' ') || 'Unknown error');\n\n if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$\n Error.captureStackTrace(this, exports.assert);\n }\n }\n};\n","'use strict';\n\nconst internals = {};\n\n\nmodule.exports = function (...args) {\n\n try {\n return JSON.stringify.apply(null, args);\n }\n catch (err) {\n return '[Cannot display object: ' + err.message + ']';\n }\n};\n","'use strict';\n\nconst Assert = require('@hapi/hoek/lib/assert');\n\n\nconst internals = {};\n\n\nexports.Sorter = class {\n\n constructor() {\n\n this._items = [];\n this.nodes = [];\n }\n\n add(nodes, options) {\n\n options = options || {};\n\n // Validate rules\n\n const before = [].concat(options.before || []);\n const after = [].concat(options.after || []);\n const group = options.group || '?';\n const sort = options.sort || 0; // Used for merging only\n\n Assert(!before.includes(group), `Item cannot come before itself: ${group}`);\n Assert(!before.includes('?'), 'Item cannot come before unassociated items');\n Assert(!after.includes(group), `Item cannot come after itself: ${group}`);\n Assert(!after.includes('?'), 'Item cannot come after unassociated items');\n\n if (!Array.isArray(nodes)) {\n nodes = [nodes];\n }\n\n for (const node of nodes) {\n const item = {\n seq: this._items.length,\n sort,\n before,\n after,\n group,\n node\n };\n\n this._items.push(item);\n }\n\n // Insert event\n\n if (!options.manual) {\n const valid = this._sort();\n Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');\n }\n\n return this.nodes;\n }\n\n merge(others) {\n\n if (!Array.isArray(others)) {\n others = [others];\n }\n\n for (const other of others) {\n if (other) {\n for (const item of other._items) {\n this._items.push(Object.assign({}, item)); // Shallow cloned\n }\n }\n }\n\n // Sort items\n\n this._items.sort(internals.mergeSort);\n for (let i = 0; i < this._items.length; ++i) {\n this._items[i].seq = i;\n }\n\n const valid = this._sort();\n Assert(valid, 'merge created a dependencies error');\n\n return this.nodes;\n }\n\n sort() {\n\n const valid = this._sort();\n Assert(valid, 'sort created a dependencies error');\n\n return this.nodes;\n }\n\n _sort() {\n\n // Construct graph\n\n const graph = {};\n const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives\n const groups = Object.create(null);\n\n for (const item of this._items) {\n const seq = item.seq; // Unique across all items\n const group = item.group;\n\n // Determine Groups\n\n groups[group] = groups[group] || [];\n groups[group].push(seq);\n\n // Build intermediary graph using 'before'\n\n graph[seq] = item.before;\n\n // Build second intermediary graph with 'after'\n\n for (const after of item.after) {\n graphAfters[after] = graphAfters[after] || [];\n graphAfters[after].push(seq);\n }\n }\n\n // Expand intermediary graph\n\n for (const node in graph) {\n const expandedGroups = [];\n\n for (const graphNodeItem in graph[node]) {\n const group = graph[node][graphNodeItem];\n groups[group] = groups[group] || [];\n expandedGroups.push(...groups[group]);\n }\n\n graph[node] = expandedGroups;\n }\n\n // Merge intermediary graph using graphAfters into final graph\n\n for (const group in graphAfters) {\n if (groups[group]) {\n for (const node of groups[group]) {\n graph[node].push(...graphAfters[group]);\n }\n }\n }\n\n // Compile ancestors\n\n const ancestors = {};\n for (const node in graph) {\n const children = graph[node];\n for (const child of children) {\n ancestors[child] = ancestors[child] || [];\n ancestors[child].push(node);\n }\n }\n\n // Topo sort\n\n const visited = {};\n const sorted = [];\n\n for (let i = 0; i < this._items.length; ++i) { // Looping through item.seq values out of order\n let next = i;\n\n if (ancestors[i]) {\n next = null;\n for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values\n if (visited[j] === true) {\n continue;\n }\n\n if (!ancestors[j]) {\n ancestors[j] = [];\n }\n\n const shouldSeeCount = ancestors[j].length;\n let seenCount = 0;\n for (let k = 0; k < shouldSeeCount; ++k) {\n if (visited[ancestors[j][k]]) {\n ++seenCount;\n }\n }\n\n if (seenCount === shouldSeeCount) {\n next = j;\n break;\n }\n }\n }\n\n if (next !== null) {\n visited[next] = true;\n sorted.push(next);\n }\n }\n\n if (sorted.length !== this._items.length) {\n return false;\n }\n\n const seqIndex = {};\n for (const item of this._items) {\n seqIndex[item.seq] = item;\n }\n\n this._items = [];\n this.nodes = [];\n\n for (const value of sorted) {\n const sortedItem = seqIndex[value];\n this.nodes.push(sortedItem.node);\n this._items.push(sortedItem);\n }\n\n return true;\n }\n};\n\n\ninternals.mergeSort = (a, b) => {\n\n return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);\n};\n","module.exports = clone;\n\n/*\n Deep clones all properties except functions\n\n var arr = [1, 2, 3];\n var subObj = {aa: 1};\n var obj = {a: 3, b: 5, c: arr, d: subObj};\n var objClone = clone(obj);\n arr.push(4);\n subObj.bb = 2;\n obj; // {a: 3, b: 5, c: [1, 2, 3, 4], d: {aa: 1}}\n objClone; // {a: 3, b: 5, c: [1, 2, 3], d: {aa: 1, bb: 2}}\n*/\n\nfunction clone(obj) {\n if (typeof obj == 'function') {\n return obj;\n }\n var result = Array.isArray(obj) ? [] : {};\n for (var key in obj) {\n // include prototype properties\n var value = obj[key];\n var type = {}.toString.call(value).slice(8, -1);\n if (type == 'Array' || type == 'Object') {\n result[key] = clone(value);\n } else if (type == 'Date') {\n result[key] = new Date(value.getTime());\n } else if (type == 'RegExp') {\n result[key] = RegExp(value.source, getRegExpFlags(value));\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction getRegExpFlags(regExp) {\n if (typeof regExp.source.flags == 'string') {\n return regExp.source.flags;\n } else {\n var flags = [];\n regExp.global && flags.push('g');\n regExp.ignoreCase && flags.push('i');\n regExp.multiline && flags.push('m');\n regExp.sticky && flags.push('y');\n regExp.unicode && flags.push('u');\n return flags.join('');\n }\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default '0.14.0';\n","/**\n * @module\n * @private\n */\n\n/**\n * Base class for all registries.\n *\n * LocusZoom is plugin-extensible, and layouts are JSON-serializable objects that refer to desired features by name (not by class).\n * This is achieved through the use of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar to make it easier to, eg, modify layouts or create classes.\n * This class is documented solely so that those helper methods can be referenced.\n */\nclass RegistryBase {\n constructor() {\n this._items = new Map();\n }\n\n /**\n * Return the registry member. If the registry stores classes, this returns the class, not the instance.\n * @param {String} name\n * @returns {Function}\n */\n get(name) {\n if (!this._items.has(name)) {\n throw new Error(`Item not found: ${name}`);\n }\n return this._items.get(name);\n }\n\n /**\n * Add a new item to the registry\n * @param {String} name The name of the item to add to the registry\n * @param {*} item The item to be added (constructor, value, etc)\n * @param {boolean} [override=false] Allow redefining an existing item?\n * @return {*} The actual object as added to the registry\n */\n add(name, item, override = false) {\n if (!override && this._items.has(name)) {\n throw new Error(`Item ${name} is already defined`);\n }\n this._items.set(name, item);\n return item;\n }\n\n /**\n * Remove a datasource from the registry (if present)\n * @param {String} name\n * @returns {boolean} True if item removed, false if item was never present\n */\n remove(name) {\n return this._items.delete(name);\n }\n\n /**\n * Check whether the specified item is registered\n * @param {String} name\n * @returns {boolean}\n */\n has(name) {\n return this._items.has(name);\n }\n\n /**\n * Names of each allowed\n * @returns {String[]}\n */\n list() {\n return Array.from(this._items.keys());\n }\n}\n\n/**\n * A specialized registry whose members are class constructors. Contains helper methods for creating instances\n * and subclasses.\n * @ignore\n */\nclass ClassRegistry extends RegistryBase {\n /**\n * Create an instance of the specified class from the registry\n * @param {String} name\n * @param {*} args Any additional arguments to be passed to the constructor\n * @returns {*}\n */\n create(name, ...args) {\n const base = this.get(name);\n return new base(...args);\n }\n\n /**\n * Create a new child class for an item in the registry.\n *\n * This is (almost, but not quite) a compatibility layer for old sites that used locuszoom\n *\n * This is primarily aimed at low-tooling environments. It is syntactic sugar, roughly equivalent to:\n * `registry.get(base); registry.add(name, class A extends base {});`\n *\n * Because this bypasses es6 class mechanics, certain things, esp super calls, may not work as well as using the\n * \"real\" class expression. This method is provided solely for convenience.\n *\n * This method is a compatibility layer for old versions. Born to be deprecated!\n * @deprecated\n * @param {string} parent_name The name of the desired parent class as represented in the registry\n * @param {string} source_name The desired name of the class to be created, as it will be named in the registry\n * @param {object} overrides An object\n * @return {*}\n */\n extend(parent_name, source_name, overrides) {\n console.warn('Deprecation warning: .extend method will be removed in future versions, in favor of explicit ES6 subclasses');\n if (arguments.length !== 3) {\n throw new Error('Invalid arguments to .extend');\n }\n\n const base = this.get(parent_name);\n class sub extends base {}\n Object.assign(sub.prototype, overrides, base);\n this.add(source_name, sub);\n return sub;\n }\n}\n\n\nexport default RegistryBase;\nexport {RegistryBase, ClassRegistry};\n","/**\n * Implement an LRU Cache\n */\n\n\nclass LLNode {\n /**\n * A single node in the linked list. Users will only need to deal with this class if using \"approximate match\" (`cache.find()`)\n * @memberOf module:undercomplicate\n * @param {string} key\n * @param {*} value\n * @param {object} metadata\n * @param {LLNode} prev\n * @param {LLNode} next\n */\n constructor(key, value, metadata = {}, prev = null, next = null) {\n this.key = key;\n this.value = value;\n this.metadata = metadata;\n this.prev = prev;\n this.next = next;\n }\n}\n\nclass LRUCache {\n /**\n * Least-recently used cache implementation, with \"approximate match\" semantics\n * @memberOf module:undercomplicate\n * @param {number} [max_size=3]\n */\n constructor(max_size = 3) {\n this._max_size = max_size;\n this._cur_size = 0; // replace with map.size so we aren't managing manually?\n this._store = new Map();\n\n // Track LRU state\n this._head = null;\n this._tail = null;\n\n // Validate options\n if (max_size === null || max_size < 0) {\n throw new Error('Cache \"max_size\" must be >= 0');\n }\n }\n\n /**\n * Check key membership without updating LRU\n * @param key\n * @returns {boolean}\n */\n has(key) {\n return this._store.has(key);\n }\n\n /**\n * Retrieve value from cache (if present) and update LRU cache to say an item was recently used\n * @param key\n * @returns {null|*}\n */\n get(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return null;\n }\n if (this._head !== cached) {\n // Rewrite the cached value to ensure it is head of the list\n this.add(key, cached.value);\n }\n return cached.value;\n }\n\n /**\n * Add an item. Forcibly replaces the existing cached value for the same key.\n * @param key\n * @param value\n * @param {Object} [metadata={}) Metadata associated with an item. Metadata can be used for lookups (`cache.find`) to test for a cache hit based on non-exact match\n */\n add(key, value, metadata = {}) {\n if (this._max_size === 0) {\n // Don't cache items if cache has 0 size.\n return;\n }\n\n const prior = this._store.get(key);\n if (prior) {\n this._remove(prior);\n }\n\n const node = new LLNode(key, value, metadata, null, this._head);\n\n if (this._head) {\n this._head.prev = node;\n } else {\n this._tail = node;\n }\n\n this._head = node;\n this._store.set(key, node);\n\n if (this._max_size >= 0 && this._cur_size >= this._max_size) {\n const old = this._tail;\n this._tail = this._tail.prev;\n this._remove(old);\n }\n this._cur_size += 1;\n }\n\n\n // Cache manipulation methods\n clear() {\n this._head = null;\n this._tail = null;\n this._cur_size = 0;\n this._store = new Map();\n }\n\n // Public method, remove by key\n remove(key) {\n const cached = this._store.get(key);\n if (!cached) {\n return false;\n }\n this._remove(cached);\n return true;\n }\n\n // Internal implementation, useful when working on list\n _remove(node) {\n if (node.prev !== null) {\n node.prev.next = node.next;\n } else {\n this._head = node.next;\n }\n\n if (node.next !== null) {\n node.next.prev = node.prev;\n } else {\n this._tail = node.prev;\n }\n this._store.delete(node.key);\n this._cur_size -= 1;\n }\n\n /**\n * Find a matching item in the cache, or return null. This is useful for \"approximate match\" semantics,\n * to check if something qualifies as a cache hit despite not having the exact same key.\n * (Example: zooming into a region, where the smaller region is a subset of something already cached)\n * @param {function} match_callback A function to be used to test the node as a possible match (returns true or false)\n * @returns {null|LLNode}\n */\n find(match_callback) {\n let node = this._head;\n while (node) {\n const next = node.next;\n if (match_callback(node)) {\n return node;\n }\n node = next;\n }\n }\n}\n\nexport { LRUCache };\n","/**\n * @private\n */\n\nimport justclone from 'just-clone';\n\n/**\n * The \"just-clone\" library only really works for objects and arrays. If given a string, it would mess things up quite a lot.\n * @param {object} data\n * @returns {*}\n */\nfunction clone(data) {\n if (typeof data !== 'object') {\n return data;\n }\n return justclone(data);\n}\n\nexport { clone };\n","/**\n * Perform a series of requests, respecting order of operations\n * @private\n */\n\nimport {Sorter} from '@hapi/topo';\n\n\nfunction _parse_declaration(spec) {\n // Parse a dependency declaration like `assoc` or `ld(assoc)` or `join(assoc, ld)`. Return node and edges that can be used to build a graph.\n const parsed = /^(?\\w+)$|((?\\w+)+\\(\\s*(?[^)]+?)\\s*\\))/.exec(spec);\n if (!parsed) {\n throw new Error(`Unable to parse dependency specification: ${spec}`);\n }\n\n let {name_alone, name_deps, deps} = parsed.groups;\n if (name_alone) {\n return [name_alone, []];\n }\n\n deps = deps.split(/\\s*,\\s*/);\n return [name_deps, deps];\n}\n\n/**\n * Perform a request for data from a set of providers, taking into account dependencies between requests.\n * This can be a mix of network requests or other actions (like join tasks), provided that the provider be some\n * object that implements a method `instance.getData`\n *\n * Each data layer in LocusZoom will translate the internal configuration into a format used by this function.\n * This function is never called directly in custom user code. In locuszoom, Requester Handles You\n *\n * TODO Future: It would be great to add a warning if the final element in the DAG does not reference all dependencies. This is a limitation of the current toposort library we use.\n *\n * @param {object} shared_options Options passed globally to all requests. In LocusZoom, this is often a copy of \"plot.state\"\n * @param {Map} entities A lookup of named entities that implement the method `instance.getData -> Promise`\n * @param {String[]} dependencies A description of how to fetch entities, and what they depend on, like `['assoc', 'ld(assoc)']`.\n * **Order will be determined by a DAG and the last item in the DAG is all that is returned.**\n * @param {boolean} [consolidate=true] Whether to return all results (almost never used), or just the last item in the resolved DAG.\n * This can be a pitfall in common usage: if you forget a \"join/consolidate\" task, the last result may appear to be missing some data.\n * @returns {Promise}\n */\nfunction getLinkedData(shared_options, entities, dependencies, consolidate = true) {\n if (!dependencies.length) {\n return [];\n }\n\n const parsed = dependencies.map((spec) => _parse_declaration(spec));\n const dag = new Map(parsed);\n\n // Define the order to perform requests in, based on a DAG\n const toposort = new Sorter();\n for (let [name, deps] of dag.entries()) {\n try {\n toposort.add(name, {after: deps, group: name});\n } catch (e) {\n throw new Error(`Invalid or possible circular dependency specification for: ${name}`);\n }\n }\n const order = toposort.nodes;\n\n // Verify that all requested entities exist by name!\n const responses = new Map();\n for (let name of order) {\n const provider = entities.get(name);\n if (!provider) {\n throw new Error(`Data has been requested from source '${name}', but no matching source was provided`);\n }\n\n // Each promise should only be triggered when the things it depends on have been resolved\n const depends_on = dag.get(name) || [];\n const prereq_promises = Promise.all(depends_on.map((name) => responses.get(name)));\n\n const this_result = prereq_promises.then((prior_results) => {\n // Each request will be told the name of the provider that requested it. This can be used during post-processing,\n // eg to use the same endpoint adapter twice and label where the fields came from (assoc.id, assoc2.id)\n // This has a secondary effect: it ensures that any changes made to \"shared\" options in one adapter will\n // not leak out to others via a mutable shared object reference.\n const options = Object.assign({_provider_name: name}, shared_options);\n return provider.getData(options, ...prior_results);\n });\n responses.set(name, this_result);\n }\n return Promise.all([...responses.values()])\n .then((all_results) => {\n if (consolidate) {\n // Some usages- eg fetch + data join tasks- will only require the last response in the sequence\n // Consolidate mode is the common use case, since returning a list of responses is not so helpful (depends on order of request, not order specified)\n return all_results[all_results.length - 1];\n }\n return all_results;\n });\n}\n\n\nexport {getLinkedData};\n\n// For testing only\nexport {_parse_declaration};\n","/**\n * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.\n */\nimport { clone } from './util';\n\n\n/**\n * Simple grouping function, used to identify sets of records for joining.\n *\n * Used internally by join helpers, exported mainly for unit testing\n * @memberOf module:undercomplicate\n * @param {object[]} records\n * @param {string} group_key\n * @returns {Map}\n */\nfunction groupBy(records, group_key) {\n const result = new Map();\n for (let item of records) {\n const item_group = item[group_key];\n\n if (typeof item_group === 'undefined') {\n throw new Error(`All records must specify a value for the field \"${group_key}\"`);\n }\n if (typeof item_group === 'object') {\n // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)\n throw new Error('Attempted to group on a field with non-primitive values');\n }\n\n let group = result.get(item_group);\n if (!group) {\n group = [];\n result.set(item_group, group);\n }\n group.push(item);\n }\n return result;\n}\n\n\nfunction _any_match(type, left, right, left_key, right_key) {\n // Helper that consolidates logic for all three join types\n const right_index = groupBy(right, right_key);\n const results = [];\n for (let item of left) {\n const left_match_value = item[left_key];\n const right_matches = right_index.get(left_match_value) || [];\n if (right_matches.length) {\n // Record appears on both left and right; equiv to an inner join\n results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));\n } else if (type !== 'inner') {\n // Record appears on left but not right\n results.push(clone(item));\n }\n }\n\n if (type === 'outer') {\n // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side\n const left_index = groupBy(left, left_key);\n for (let item of right) {\n const right_match_value = item[right_key];\n const left_matches = left_index.get(right_match_value) || [];\n if (!left_matches.length) {\n results.push(clone(item));\n }\n }\n }\n return results;\n}\n\n/**\n * Equivalent to LEFT OUTER JOIN in SQL. Return all left records, joined to matching right data where appropriate.\n * @memberOf module:undercomplicate\n * @param {Object[]} left The left side recordset\n * @param {Object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction left_match(left, right, left_key, right_key) {\n return _any_match('left', ...arguments);\n}\n\n/**\n * Equivalent to INNER JOIN in SQL. Only return record joins if the key field has a match on both left and right.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction inner_match(left, right, left_key, right_key) {\n return _any_match('inner', ...arguments);\n}\n\n/**\n * Equivalent to FULL OUTER JOIN in SQL. Return records in either recordset, joined where appropriate.\n * @memberOf module:undercomplicate\n * @param {object[]} left The left side recordset\n * @param {object[]} right The right side recordset\n * @param {string} left_key The join field in left records\n * @param {string} right_key The join field in right records\n * @returns {Object[]}\n */\nfunction full_outer_match(left, right, left_key, right_key) {\n return _any_match('outer', ...arguments);\n}\n\nexport {left_match, inner_match, full_outer_match, groupBy};\n","/**\n * Parse useful entities\n */\n\n/**\n * @private\n */\nconst REGEX_MARKER = /^(?:chr)?([a-zA-Z0-9]+?)[_:-](\\d+)[_:|-]?(\\w+)?[/_:|-]?([^_]+)?_?(.*)?/;\n\n/**\n * Parse a single marker, cleaning up values as necessary\n * @private\n * @param {String} value\n * @param {boolean} test If called in testing mode, do not throw an exception\n * @returns {Array} chr, pos, ref, alt (if match found; ref and alt optional)\n */\nfunction parseMarker(value, test = false) {\n const match = value && value.match(REGEX_MARKER);\n if (match) {\n return match.slice(1);\n }\n if (!test) {\n throw new Error(`Could not understand marker format for ${value}. Should be of format chr:pos or chr:pos_ref/alt`);\n } else {\n return null;\n }\n}\n\n/**\n * Normalize a provided variant string into the EPACTS-style `chrom:pos_ref/alt` format expected by LocusZoom and the Michigan LD Server\n * This allows harmonizing various input data to a consistent format\n * @private\n * @param {String} variant A string that specifies variant information in one of several common formats (like chr1:99_A/C, 1-99-A-C, 1:99:A:C, etc)\n */\nfunction normalizeMarker(variant) {\n const match = parseMarker(variant);\n if (!match) {\n throw new Error(`Unable to normalize marker format for variant: ${variant}`);\n }\n const [chrom, pos, ref, alt] = match;\n let normalized = `${chrom}:${pos}`;\n if (ref && alt) {\n normalized += `_${ref}/${alt}`;\n }\n return normalized;\n}\n\n\nexport {\n parseMarker,\n normalizeMarker,\n};\n","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n *\n * ## Adapters are responsible for retrieving data\n * In LocusZoom, the act of fetching data (from API, JSON file, or Tabix) is separate from the act of rendering data.\n * Adapters are used to handle retrieving from different sources, and can provide various advanced functionality such\n * as caching, data harmonization, and annotating API responses with calculated fields. They can also be used to join\n * two data sources, such as annotating association summary statistics with LD information.\n *\n * Most of LocusZoom's builtin layouts and adapters are written for the field names and data formats of the\n * UMich [PortalDev API](https://portaldev.sph.umich.edu/docs/api/v1/#introduction):\n * if your data is in a different format, an adapter can be used to coerce or rename fields.\n * Although it is possible to change every part of a rendering layout to expect different fields, this is often much\n * more work than providing data in the expected format.\n *\n * ## Creating data adapters\n * The documentation in this section describes the available data types and adapters. Real LocusZoom usage almost never\n * creates these classes directly: rather, they are defined from configuration objects that ask for a source by name.\n *\n * The below example creates an object responsible for fetching two different GWAS summary statistics datasets from two different API endpoints, for any data\n * layer that asks for fields from `trait1:fieldname` or `trait2:fieldname`.\n *\n * ```\n * const data_sources = new LocusZoom.DataSources();\n * data_sources.add(\"trait1\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 1 }]);\n * data_sources.add(\"trait2\", [\"AssociationLZ\", { url: \"http://server.com/api/single/\", source: 2 }]);\n * ```\n *\n * These data sources are then passed to the plot when data is to be rendered:\n * `const plot = LocusZoom.populate(\"#lz-plot\", data_sources, layout);`\n *\n * @module LocusZoom_Adapters\n */\n\nimport {BaseUrlAdapter} from './undercomplicate';\n\nimport {parseMarker} from '../helpers/parse';\n\n/**\n * Replaced with the BaseLZAdapter class.\n * @public\n * @deprecated\n */\nclass BaseAdapter {\n constructor() {\n throw new Error('The \"BaseAdapter\" and \"BaseApiAdapter\" classes have been replaced in LocusZoom 0.14. See migration guide for details.');\n }\n}\n\n/**\n * Removed class for LocusZoom data adapters that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n * @extends module:LocusZoom_Adapters~BaseAdapter\n * @deprecated\n * @param {string} config.url The URL for the remote dataset. By default, most adapters perform a GET request.\n * @inheritDoc\n */\nclass BaseApiAdapter extends BaseAdapter {}\n\n\n/**\n * @extends module:undercomplicate.BaseUrlAdapter\n * @inheritDoc\n */\nclass BaseLZAdapter extends BaseUrlAdapter {\n /**\n * @param [config.cache_enabled=true]\n * @param [config.cache_size=3]\n * @param [config.url]\n * @param [config.prefix_namespace=true] Whether to modify the API response by prepending namespace to each field name.\n * Most adapters will do this by default, so that each field is unambiguously defined based on where it comes from. (this helps to disambiguate two providers that return similar field names, like assoc:variant and catalog:variant)\n * Typically, this is only disabled if the response payload is very unusual\n * @param {String[]} [config.limit_fields=null] If an API returns far more data than is needed, this can be used to simplify\n * the payload by excluding unused fields. This can help to reduce memory usage for really big server responses like LD.\n */\n constructor(config = {}) {\n if (config.params) {\n // Backwards-compat: prevent old sources from breaking in subtle ways because config options are no longer split between two places.\n console.warn('Deprecation warning: all options in \"config.params\" should now be specified as top level keys.');\n Object.assign(config, config.params || {});\n delete config.params; // fields are moved, not just copied in both places; Custom code will need to reflect new reality!\n }\n super(config);\n\n // Prefix the namespace for this source to all fieldnames: id -> assoc.id\n // This is useful for almost all layers because the layout object says where to find every field, exactly.\n // For some very complex data structure- mainly the Genes API payload- the datalayer might want to operate on\n // that complex set of fields directly. Disable _prefix_namespace to get field names as they appear\n // in the response. (gene_name instead of genes.gene_name)\n const { prefix_namespace = true, limit_fields } = config;\n this._prefix_namespace = prefix_namespace;\n this._limit_fields = limit_fields ? new Set(limit_fields) : false; // Optional and typically only used for very standard datasets like LD or catalog, where API returns >> what is displayed. People want to show their own custom annos for assoc plots pretty often, so the most-often-customized adapters don't specify limit_fields\n }\n\n /**\n * Determine how a particular request will be identified in cache. Most LZ requests are region based,\n * so the default is a string concatenation of `chr_start_end`. This adapter is \"region aware\"- if the user\n * zooms in, it won't trigger a network request because we alread have the data needed.\n * @param options Receives plot.state plus any other request options defined by this source\n * @returns {string}\n * @public\n */\n _getCacheKey(options) {\n // Most LZ adapters are fetching REGION data, and it makes sense to treat zooming as a cache hit by default\n let {chr, start, end} = options; // Current view: plot.state\n\n // Does a prior cache hit qualify as a superset of the ROI?\n const superset = this._cache.find(({metadata: md}) => chr === md.chr && start >= md.start && end <= md.end);\n if (superset) {\n ({ chr, start, end } = superset.metadata);\n }\n\n // The default cache key is region-based, and this method only returns the region-based part of the cache hit\n // That way, methods that override the key can extend the base value and still get the benefits of region-overlap-check\n options._cache_meta = { chr, start, end };\n return `${chr}_${start}_${end}`;\n }\n\n /**\n * Add the \"local namespace\" as a prefix for every field returned for this request. Eg if the association api\n * returns a field called variant, and the source is referred to as \"assoc\" within a particular data layer, then\n * the returned records will have a field called \"assoc:variant\"\n *\n * @param records\n * @param options\n * @returns {*}\n * @public\n */\n _postProcessResponse(records, options) {\n if (!this._prefix_namespace || !Array.isArray(records)) {\n return records;\n }\n\n // Transform fieldnames to include the namespace name as a prefix. For example, a data layer that asks for\n // assoc data might see \"variant\" as \"assoc.variant\"\n const { _limit_fields } = this;\n const { _provider_name } = options;\n\n return records.map((row) => {\n return Object.entries(row).reduce(\n (acc, [label, value]) => {\n // Rename API fields to format `namespace:fieldname`. If an adapter specifies limit_fields, then remove any unused API fields from the final payload.\n if (!_limit_fields || _limit_fields.has(label)) {\n acc[`${_provider_name}:${label}`] = value;\n }\n return acc;\n },\n {},\n );\n });\n }\n\n /**\n * Convenience method, manually called in LZ sources that deal with dependent data.\n *\n * In the last step of fetching data, LZ adds a prefix to each field name.\n * This means that operations like \"build query based on prior data\" can't just ask for \"log_pvalue\" because\n * they are receiving \"assoc:log_pvalue\" or some such unknown prefix.\n *\n * This helper lets us use dependent data more easily. Not every adapter needs to use this method.\n *\n * @param {Object} a_record One record (often the first one in a set of records)\n * @param {String} fieldname The desired fieldname, eg \"log_pvalue\"\n */\n _findPrefixedKey(a_record, fieldname) {\n const suffixer = new RegExp(`:${fieldname}$`);\n const match = Object.keys(a_record).find((key) => suffixer.test(key));\n if (!match) {\n throw new Error(`Could not locate the required key name: ${fieldname} in dependent data`);\n }\n return match;\n }\n}\n\n\n/**\n * The base adapter for the UMich Portaldev API server. This adds a few custom behaviors that handle idiosyncrasies\n * of one particular web server.\n * @extends module:LocusZoom_Adapters~BaseLZAdapter\n * @inheritDoc\n */\nclass BaseUMAdapter extends BaseLZAdapter {\n /**\n * @param {Object} config\n * @param {String} [config.build] The genome build to be used by all requests for this adapter. (UMich APIs are all genome build aware). \"GRCh37\" or \"GRCh38\"\n */\n constructor(config = {}) {\n super(config);\n // The UM portaldev API accepts an (optional) parameter \"genome_build\"\n this._genome_build = config.genome_build || config.build;\n }\n\n _validateBuildSource(build, source) {\n // Build OR Source, not both\n if ((build && source) || !(build || source)) {\n throw new Error(`${this.constructor.name} must provide a parameter specifying either \"build\" or \"source\". It should not specify both.`);\n }\n // If the build isn't recognized, our APIs can't transparently select a source to match\n if (build && !['GRCh37', 'GRCh38'].includes(build)) {\n throw new Error(`${this.constructor.name} must specify a valid 'genome_build'`);\n }\n }\n\n // Special behavior for the UM portaldev API: col -> row format normalization\n /**\n * Some endpoints in the UM portaldev API returns columns of data, rather than rows. Convert the response to record objects, each row of a table being represented as an object of {field:value} pairs.\n * @param response_text\n * @param options\n * @returns {Object[]}\n * @public\n */\n _normalizeResponse(response_text, options) {\n let data = super._normalizeResponse(...arguments);\n // Most portaldev endpoints (though not all) store the desired response in just one specific part of the payload\n data = data.data || data;\n\n if (Array.isArray(data)) {\n // Already in the desired form\n return data;\n }\n // Otherwise, assume the server response is an object representing columns of data.\n // Each array should have the same length (verify), and a given array index corresponds to a single row.\n const keys = Object.keys(data);\n const N = data[keys[0]].length;\n const sameLength = keys.every(function (key) {\n const item = data[key];\n return item.length === N;\n });\n if (!sameLength) {\n throw new Error(`${this.constructor.name} expects a response in which all arrays of data are the same length`);\n }\n\n // Go down the columns, and create an object for each row record\n const records = [];\n const fields = Object.keys(data);\n for (let i = 0; i < N; i++) {\n const record = {};\n for (let j = 0; j < fields.length; j++) {\n record[fields[j]] = data[fields[j]][i];\n }\n records.push(record);\n }\n return records;\n }\n}\n\n\n/**\n * Retrieve Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a request\n * to a specific REST API.\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @inheritDoc\n *\n * @param {Number} config.source The source ID for the dataset of interest, used to construct the request URL\n */\nclass AssociationLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // We don't validate the source option because a depressing number of people use AssociationLZ to serve non-dynamic JSON files\n const { source } = config;\n this._source_id = source;\n }\n\n _getURL (request_options) {\n const {chr, start, end} = request_options;\n const base = super._getURL(request_options);\n return `${base}results/?filter=analysis in ${this._source_id} and chromosome in '${chr}' and position ge ${start} and position le ${end}`;\n }\n}\n\n\n/**\n * Fetch GWAS catalog data for a list of known variants, and align the data with previously fetched association data.\n * There can be more than one claim per variant; this adapter is written to support a visualization in which each\n * association variant is labeled with the single most significant hit in the GWAS catalog. (and enough information to link to the external catalog for more information)\n *\n * Sometimes the GWAS catalog uses rsIDs that could refer to more than one variant (eg multiple alt alleles are\n * possible for the same rsID). To avoid missing possible hits due to ambiguous meaning, we connect the assoc\n * and catalog data via the position field, not the full variant specifier. This source will auto-detect the matching\n * field in association data by looking for the field name `position` or `pos`.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GwasCatalogLZ extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build] The genome build to use when requesting the specific genomic region.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen catalog. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent EBI GWAS catalog, GRCh37.\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['log_pvalue', 'pos', 'rsid', 'trait', 'variant'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n const source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id eq ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?format=objects&sort=pos&filter=chrom eq '${request_options.chr}' and pos ge ${request_options.start} and pos le ${request_options.end}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen gene dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent GENCODE data, GRCh37.\n */\nclass GeneLZ extends BaseUMAdapter {\n constructor(config = {}) {\n super(config);\n\n // The UM Genes API has a very complex internal format and the genes layer is written to work with it exactly as given.\n // We will avoid transforming or modifying the payload.\n this._prefix_namespace = false;\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and source in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chrom eq '${request_options.chr}' and start le ${request_options.end} and end ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve Gene Constraint Data, as fetched from the gnomAD server (or compatible graphQL api endpoint)\n *\n * This is intended to be the second request in a chain, with special logic that connects it to Genes data\n * already fetched. It assumes that the genes data is returned from the UM API, and thus the logic involves\n * matching on specific assumptions about `gene_name` format.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass GeneConstraintLZ extends BaseLZAdapter {\n /**\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n */\n constructor(config = {}) {\n super(config);\n this._prefix_namespace = false;\n }\n\n _buildRequestOptions(state, genes_data) {\n const build = state.genome_build || this._config.build;\n if (!build) {\n throw new Error(`Adapter ${this.constructor.name} must specify a 'genome_build' option`);\n }\n\n const unique_gene_names = new Set();\n for (let gene of genes_data) {\n // In rare cases, the same gene symbol may appear at multiple positions. (issue #179) We de-duplicate the\n // gene names to avoid issuing a malformed GraphQL query.\n unique_gene_names.add(gene.gene_name);\n }\n\n state.query = [...unique_gene_names.values()].map(function (gene_name) {\n // GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268\n const alias = `_${gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`;\n // Each gene symbol is a separate graphQL query, grouped into one request using aliases\n return `${alias}: gene(gene_symbol: \"${gene_name}\", reference_genome: ${build}) { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } `;\n });\n state.build = build;\n return Object.assign({}, state);\n }\n\n _performRequest(options) {\n let {query, build} = options;\n if (!query.length || query.length > 25 || build === 'GRCh38') {\n // Skip the API request when it would make no sense:\n // - Build 38 (gnomAD supports build GRCh37 only; don't hit server when invalid. This isn't future proof, but we try to be good neighbors.)\n // - Too many genes (gnomAD appears to set max cost ~25 genes)\n // - No genes in region (hence no constraint info)\n return Promise.resolve([]);\n }\n query = `{${query.join(' ')} }`; // GraphQL isn't quite JSON; items are separated by spaces but not commas\n\n const url = this._getURL(options);\n\n // See: https://graphql.org/learn/serving-over-http/\n const body = JSON.stringify({ query: query });\n const headers = { 'Content-Type': 'application/json' };\n\n // Note: The gnomAD API sometimes fails randomly.\n // If request blocked, return a fake \"no data\" signal so the genes track can still render w/o constraint info\n return fetch(url, { method: 'POST', body, headers }).then((response) => {\n if (!response.ok) {\n return [];\n }\n return response.text();\n }).catch((err) => []);\n }\n\n /**\n * The gnomAD API has a very complex internal data format. Bypass any record parsing or transform steps.\n */\n _normalizeResponse(response_text) {\n if (typeof response_text !== 'string') {\n // If the query short-circuits, we receive an empty list instead of a string\n return response_text;\n }\n const data = JSON.parse(response_text);\n return data.data;\n }\n}\n\n\n/**\n * Fetch linkage disequilibrium information from a UMich LDServer-compatible API, relative to a reference variant.\n * If no `plot.state.ldrefvar` is explicitly provided, this source will attempt to find the most significant GWAS\n * variant and yse that as the LD reference variant.\n *\n * THIS ADAPTER EXPECTS TO RECEIVE ASSOCIATION DATA WITH FIELDS `variant` and `log_pvalue`. It may not work correctly\n * if this information is not provided.\n *\n * This source is designed to connect its results to association data, and therefore depends on association data having\n * been loaded by a previous request. For custom association APIs, some additional options might\n * need to be be specified in order to locate the most significant SNP. Variant IDs of the form `chrom:pos_ref/alt`\n * are preferred, but this source will attempt to harmonize other common data formats into something that the LD\n * server can understand.\n *\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n */\nclass LDServer extends BaseUMAdapter {\n /**\n * @param {string} config.url The base URL for the remote data.\n * @param [config.build='GRCh37'] The genome build to use when calculating LD relative to a specified reference variant.\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param [config.source='1000G'] The name of the reference panel to use, as specified in the LD server instance.\n * May be overridden by a global parameter `plot.state.ld_source` to implement widgets that alter LD display.\n * @param [config.population='ALL'] The sample population used to calculate LD for a specified source;\n * population names vary depending on the reference panel and how the server was populated wth data.\n * May be overridden by a global parameter `plot.state.ld_pop` to implement widgets that alter LD display.\n * @param [config.method='rsquare'] The metric used to calculate LD\n */\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['variant2', 'position2', 'correlation'];\n }\n super(config);\n }\n\n __find_ld_refvar(state, assoc_data) {\n const assoc_variant_name = this._findPrefixedKey(assoc_data[0], 'variant');\n const assoc_logp_name = this._findPrefixedKey(assoc_data[0], 'log_pvalue');\n\n // Determine the reference variant (via user selected OR automatic-per-track)\n let refvar;\n let best_hit = {};\n if (state.ldrefvar) {\n // State/ldrefvar would store the variant in the format used by assoc data, so no need to clean up to match in data\n refvar = state.ldrefvar;\n best_hit = assoc_data.find((item) => item[assoc_variant_name] === refvar) || {};\n } else {\n // find highest log-value and associated var spec\n let best_logp = 0;\n for (let item of assoc_data) {\n const { [assoc_variant_name]: variant, [assoc_logp_name]: log_pvalue} = item;\n if (log_pvalue > best_logp) {\n best_logp = log_pvalue;\n refvar = variant;\n best_hit = item;\n }\n }\n }\n\n // Add a special field that is not part of the assoc or LD data from the server, but has significance for plotting.\n // Since we already know the best hit, it's easier to do this here rather than in annotate or join phase.\n best_hit.lz_is_ld_refvar = true;\n\n // Above, we compared the ldrefvar to the assoc data. But for talking to the LD server,\n // the variant fields must be normalized to a specific format. All later LD operations will use that format.\n const match = parseMarker(refvar, true);\n if (!match) {\n throw new Error('Could not request LD for a missing or incomplete marker format');\n }\n\n const [chrom, pos, ref, alt] = match;\n // Currently, the LD server only accepts full variant specs; it won't return LD w/o ref+alt. Allowing\n // a partial match at most leaves room for potential future features.\n refvar = `${chrom}:${pos}`; // FIXME: is this a server request that we can skip?\n if (ref && alt) {\n refvar += `_${ref}/${alt}`;\n }\n\n const coord = +pos;\n // Last step: sanity check the proposed reference variant. Is it inside the view region? If not, we're probably\n // remembering a user choice from before user jumped to a new region. LD should be relative to something nearby.\n if ((coord && state.ldrefvar && state.chr) && (chrom !== String(state.chr) || coord < state.start || coord > state.end)) {\n // Rerun this method, after clearing out the proposed reference variant. NOTE: Adapter call receives a\n // *copy* of plot.state, so wiping here doesn't remove the original value.\n state.ldrefvar = null;\n return this.__find_ld_refvar(state, assoc_data);\n }\n\n // Return the reference variant, in a normalized format suitable for LDServer queries\n return refvar;\n }\n\n _buildRequestOptions(state, assoc_data) {\n if (!assoc_data) {\n throw new Error('LD request must depend on association data');\n }\n\n // If no state refvar is provided, find the most significant variant in any provided assoc data.\n // Assumes that assoc satisfies the \"assoc\" fields contract, eg has fields variant and log_pvalue\n const base = super._buildRequestOptions(...arguments);\n if (!assoc_data.length) {\n // No variants, so no need to annotate association data with LD!\n // NOTE: Revisit. This could have odd cache implications (eg, when joining two assoc datasets to LD, and only the second dataset has data in the region)\n base._skip_request = true;\n return base;\n }\n\n base.ld_refvar = this.__find_ld_refvar(state, assoc_data);\n\n // The LD source/pop can be overridden from plot.state, so that user choices can override initial source config\n const genome_build = state.genome_build || this._config.build || 'GRCh37'; // This isn't expected to change after the data is plotted.\n let ld_source = state.ld_source || this._config.source || '1000G';\n const ld_population = state.ld_pop || this._config.population || 'ALL'; // LDServer panels will always have an ALL\n\n if (ld_source === '1000G' && genome_build === 'GRCh38') {\n // For build 38 (only), there is a newer/improved 1000G LD panel available that uses WGS data. Auto upgrade by default.\n ld_source = '1000G-FRZ09';\n }\n\n this._validateBuildSource(genome_build, null); // LD doesn't need to validate `source` option\n return Object.assign({}, base, { genome_build, ld_source, ld_population });\n }\n\n _getURL(request_options) {\n const method = this._config.method || 'rsquare';\n const {\n chr, start, end,\n ld_refvar,\n genome_build, ld_source, ld_population,\n } = request_options;\n\n const base = super._getURL(request_options);\n\n return [\n base, 'genome_builds/', genome_build, '/references/', ld_source, '/populations/', ld_population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(ld_refvar),\n '&chrom=', encodeURIComponent(chr),\n '&start=', encodeURIComponent(start),\n '&stop=', encodeURIComponent(end),\n ].join('');\n }\n\n _getCacheKey(options) {\n // LD is keyed by more than just region; append other parameters to the base cache key\n const base = super._getCacheKey(options);\n const { ld_refvar, ld_source, ld_population } = options;\n return `${base}_${ld_refvar}_${ld_source}_${ld_population}`;\n }\n\n _performRequest(options) {\n // Skip request if this one depends on other data, and we are in a region with no data\n if (options._skip_request) {\n // TODO: A skipped request leads to a cache value; possible edge cases where this could get weird.\n return Promise.resolve([]);\n }\n\n const url = this._getURL(options);\n\n // The UM LDServer uses API pagination; fetch all data by chaining requests when pages are detected\n let combined = { data: {} };\n let chainRequests = function (url) {\n return fetch(url).then().then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n }).then(function(payload) {\n payload = JSON.parse(payload);\n Object.keys(payload.data).forEach(function (key) {\n combined.data[key] = (combined.data[key] || []).concat(payload.data[key]);\n });\n if (payload.next) {\n return chainRequests(payload.next);\n }\n return combined;\n });\n };\n return chainRequests(url);\n }\n}\n\n\n/**\n * Retrieve Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param [config.build] The genome build to use\n * May be overridden by a global parameter `plot.state.genome_build` so that all datasets can be fetched for the appropriate build in a consistent way.\n * @param {Number} [config.source] The ID of the chosen dataset. Most usages should omit this parameter and\n * let LocusZoom choose the newest available dataset to use based on the genome build: defaults to recent HAPMAP recombination rate, GRCh37.\n */\nclass RecombLZ extends BaseUMAdapter {\n constructor(config) {\n if (!config.limit_fields) {\n config.limit_fields = ['position', 'recomb_rate'];\n }\n super(config);\n }\n\n /**\n * Add query parameters to the URL to construct a query for the specified region\n */\n _getURL(request_options) {\n const build = request_options.genome_build || this._config.build;\n let source = this._config.source;\n this._validateBuildSource(build, source);\n\n // If a build name is provided, it takes precedence (the API will attempt to auto-select newest dataset based on the requested genome build).\n // Build and source are mutually exclusive, because hard-coded source IDs tend to be out of date\n const source_query = build ? `&build=${build}` : ` and id in ${source}`;\n\n const base = super._getURL(request_options);\n return `${base}?filter=chromosome eq '${request_options.chr}' and position le ${request_options.end} and position ge ${request_options.start}${source_query}`;\n }\n}\n\n\n/**\n * Retrieve static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg it does not know how to join together LD and association data).\n *\n * Therefore it is the responsibility of the user to pass information in a format that can be read and\n * understood by the chosen plot- a StaticJSON source is rarely a drop-in replacement for existing layouts.\n *\n * This source is largely here for legacy reasons. More often, a convenient way to serve static data is as separate\n * JSON files to an existing source (with the JSON url in place of an API).\n *\n * Note: The name is a bit misleading. It receives JS objects, not strings serialized as \"json\".\n * @public\n * @see module:LocusZoom_Adapters~BaseLZAdapter\n * @param {object} config.data The data to be returned by this source (subject to namespacing rules)\n */\nclass StaticSource extends BaseLZAdapter {\n constructor(config = {}) {\n // Does not receive any config; the only argument is the raw data, embedded when source is created\n super(...arguments);\n const { data } = config;\n if (!data || Array.isArray(config)) { // old usages may provide an array directly instead of as config key\n throw new Error(\"'StaticSource' must provide data as required option 'config.data'\");\n }\n this._data = data;\n }\n\n _performRequest(options) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Retrieve PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @see module:LocusZoom_Adapters~BaseUMAdapter\n * @param {string} config.url The base URL for the remote data\n * @param {String[]} config.build This datasource expects to be provided the name of the genome build that will\n * be used to provide PheWAS results for this position. Note positions may not translate between builds.\n */\nclass PheWASLZ extends BaseUMAdapter {\n _getURL(request_options) {\n const build = (request_options.genome_build ? [request_options.genome_build] : null) || this._config.build;\n if (!build || !Array.isArray(build) || !build.length) {\n throw new Error(['Adapter', this.constructor.name, 'requires that you specify array of one or more desired genome build names'].join(' '));\n }\n const base = super._getURL(request_options);\n const url = [\n base,\n \"?filter=variant eq '\", encodeURIComponent(request_options.variant), \"'&format=objects&\",\n build.map(function (item) {\n return `build=${encodeURIComponent(item)}`;\n }).join('&'),\n ];\n return url.join('');\n }\n\n _getCacheKey(options) {\n // Not a region based source; don't do anything smart for cache check\n return this._getURL(options);\n }\n}\n\n// Deprecated symbols\nexport { BaseAdapter, BaseApiAdapter };\n\n// Usually used as a parent class for custom code\nexport { BaseLZAdapter, BaseUMAdapter };\n\n// Usually used as a standalone class\nexport {\n AssociationLZ,\n GeneConstraintLZ,\n GeneLZ,\n GwasCatalogLZ,\n LDServer,\n PheWASLZ,\n RecombLZ,\n StaticSource,\n};\n","import {LRUCache} from './lru_cache';\nimport {clone} from './util';\n\n/**\n * @param {boolean} [config.cache_enabled=true] Whether to enable the LRU cache, and store a copy of the normalized and parsed response data.\n * Turned on by default for most remote requests; turn off if you are using another datastore (like Vuex) or if the response body uses too much memory.\n * @param {number} [config.cache_size=3] How many requests to cache. Track-dependent annotations like LD might benefit\n * from caching more items, while very large payloads (like, um, TOPMED LD) might benefit from a smaller cache size.\n * For most LocusZoom usages, the cache is \"region aware\": zooming in will use cached data, not a separate request\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseAdapter {\n constructor(config = {}) {\n this._config = config;\n const {\n // Cache control\n cache_enabled = true,\n cache_size = 3,\n } = config;\n this._enable_cache = cache_enabled;\n this._cache = new LRUCache(cache_size);\n }\n\n /**\n * Build an object with options that control the request. This can take into account both explicit options, and prior data.\n * @param {Object} options Any global options passed in via `getData`. Eg, in locuszoom, every request is passed a copy of `plot.state` as the options object, in which case every adapter would expect certain basic information like `chr, start, end` to be available.\n * @param {Object[]} dependent_data If the source is called with dependencies, this function will receive one argument with the fully parsed response data from each other source it depends on. Eg, `ld(assoc)` means that the LD adapter would be called with the data from an association request as a function argument. Each dependency is its own argument: there can be 0, 1, 2, ...N arguments.\n * @returns {*} An options object containing initial options, plus any calculated values relevant to the request.\n * @public\n */\n _buildRequestOptions(options, dependent_data) {\n // Perform any pre-processing required that may influence the request. Receives an array with the payloads\n // for each request that preceded this one in the dependency chain\n // This method may optionally take dependent data into account. For many simple adapters, there won't be any dependent data!\n return Object.assign({}, options);\n }\n\n /**\n * Determine how this request is uniquely identified in cache. Usually this is an exact match for the same key, but it doesn't have to be.\n * The LRU cache implements a `find` method, which means that a cache item can optionally be identified by its node\n * `metadata` (instead of exact key match).\n * This is useful for situations where the user zooms in to a smaller region and wants the original request to\n * count as a cache hit. See subclasses for example.\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {*} This is often a string concatenating unique values for a compound cache key, like `chr_start_end`. If null, it is treated as a cache miss.\n * @public\n */\n _getCacheKey(options) {\n /* istanbul ignore next */\n if (this._enable_cache) {\n throw new Error('Method not implemented');\n }\n return null;\n }\n\n /**\n * Perform the act of data retrieval (eg from a URL, blob, or JSON entity)\n * @param {object} options Request options from `_buildRequestOptions`\n * @returns {Promise}\n * @public\n */\n _performRequest(options) {\n /* istanbul ignore next */\n throw new Error('Not implemented');\n }\n\n /**\n * Convert the response format into a list of objects, one per datapoint. Eg split lines of a text file, or parse a blob of json.\n * @param {*} response_text The raw response from performRequest, be it text, binary, etc. For most web based APIs, it is assumed to be text, and often JSON.\n * @param {Object} options Request options. These are not typically used when normalizing a response, but the object is available.\n * @returns {*} A list of objects, each object representing one row of data `{column_name: value_for_row}`\n * @public\n */\n _normalizeResponse(response_text, options) {\n return response_text;\n }\n\n /**\n * Perform custom client-side operations on the retrieved data. For example, add calculated fields or\n * perform rapid client-side filtering on cached data. Annotations are applied after cache, which means\n * that the same network request can be dynamically annotated/filtered in different ways in response to user interactions.\n *\n * This result is currently not cached, but it may become so in the future as responsibility for dynamic UI\n * behavior moves to other layers of the application.\n * @param {Object[]} records\n * @param {Object} options\n * @returns {*}\n * @public\n */\n _annotateRecords(records, options) {\n return records;\n }\n\n /**\n * A hook to transform the response after all operations are done. For example, this can be used to prefix fields\n * with a namespace unique to the request, like `log_pvalue` -> `assoc:log_pvalue`. (by applying namespace prefixes to field names last,\n * annotations and validation can happen on the actual API payload, without having to guess what the fields were renamed to).\n * @param records\n * @param options\n * @public\n */\n _postProcessResponse(records, options) {\n return records;\n }\n\n /**\n * All adapters must implement this method to asynchronously return data. All other methods are simply internal hooks to customize the actual request for data.\n * @param {object} options Shared options for this request. In LocusZoom, this is typically a copy of `plot.state`.\n * @param {Array[]} dependent_data Zero or more recordsets corresponding to each individual adapter that this one depends on.\n * Can be used to build a request that takes into account prior data.\n * @returns {Promise<*>}\n */\n getData(options = {}, ...dependent_data) {\n // Public facing method to define, perform, and process the request\n options = this._buildRequestOptions(options, ...dependent_data);\n\n const cache_key = this._getCacheKey(options);\n\n // Then retrieval and parse steps: parse + normalize response, annotate\n let result;\n if (this._enable_cache && this._cache.has(cache_key)) {\n result = this._cache.get(cache_key);\n } else {\n // Cache the promise (to avoid race conditions in conditional fetch). If anything (like `_getCacheKey`)\n // sets a special option value called `_cache_meta`, this will be used to annotate the cache entry\n // For example, this can be used to decide whether zooming into a view could be satisfied by a cache entry,\n // even if the actual cache key wasn't an exact match. (see subclasses for an example; this class is generic)\n result = Promise.resolve(this._performRequest(options))\n // Note: we cache the normalized (parsed) response\n .then((text) => this._normalizeResponse(text, options));\n this._cache.add(cache_key, result, options._cache_meta);\n // We are caching a promise, which means we want to *un*cache a promise that rejects, eg a failed or interrupted request\n // Otherwise, temporary failures couldn't be resolved by trying again in a moment\n // TODO: In the future, consider providing a way to skip requests (eg, a sentinel value to flag something\n // as not cacheable, like \"no dependent data means no request... but maybe in another place this is used, there will be different dependent data and a request would make sense\")\n result.catch((e) => this._cache.remove(cache_key));\n }\n\n return result\n // Return a deep clone of the data, so that there are no shared mutable references to a parsed object in cache\n .then((data) => clone(data))\n .then((records) => this._annotateRecords(records, options))\n .then((records) => this._postProcessResponse(records, options));\n }\n}\n\n\n/**\n * Fetch data over the web, usually from a REST API that returns JSON\n * @param {string} config.url The URL to request\n * @extends module:undercomplicate.BaseAdapter\n * @inheritDoc\n * @memberOf module:undercomplicate\n */\nclass BaseUrlAdapter extends BaseAdapter {\n constructor(config = {}) {\n super(config);\n this._url = config.url;\n }\n\n\n /**\n * Default cache key is the URL for this request\n * @public\n */\n _getCacheKey(options) {\n return this._getURL(options);\n }\n\n /**\n * In many cases, the base url should be modified with query parameters based on request options.\n * @param options\n * @returns {*}\n * @private\n */\n _getURL(options) {\n return this._url;\n }\n\n _performRequest(options) {\n const url = this._getURL(options);\n // Many resources will modify the URL to add query or segment parameters. Base method provides option validation.\n // (not validating in constructor allows URL adapter to be used as more generic parent class)\n if (!this._url) {\n throw new Error('Web based resources must specify a resource URL as option \"url\"');\n }\n return fetch(url).then((response) => {\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n return response.text();\n });\n }\n\n _normalizeResponse(response_text, options) {\n if (typeof response_text === 'string') {\n return JSON.parse(response_text);\n }\n // Some custom usages will return other datatypes. These would need to be handled by custom normalization logic in a subclass.\n return response_text;\n }\n}\n\nexport { BaseAdapter, BaseUrlAdapter };\n","/**\n * A registry of known data adapters. Can be used to find adapters by name. It will search predefined classes\n * as well as those registered by plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// LocusZoom.Adapters is a basic registry with no special behavior.\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters (responsible for\n * controlling the retrieval and harmonization of data).\n * @alias module:LocusZoom~Adapters\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(adapters)) {\n registry.add(name, type);\n}\n\n// Add some hard-coded aliases for backwards compatibility\n\n/**\n * Backwards-compatible alias for StaticSource\n * @public\n * @name module:LocusZoom_Adapters~StaticJSON\n * @see module:LocusZoom_Adapters~StaticSource\n */\nregistry.add('StaticJSON', adapters.StaticSource);\n\n/**\n * Backwards-compatible alias for LDServer\n * @public\n * @name module:LocusZoom_Adapters~LDLZ2\n * @see module:LocusZoom_Adapters~LDServer\n */\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","const __WEBPACK_NAMESPACE_OBJECT__ = d3;","/**\n * Available statuses that individual elements can have. Each status is described by\n * a verb and an adjective. Verbs are used to generate data layer\n * methods for updating the status on one or more elements. Adjectives are used in class\n * names and applied or removed from elements to have a visual representation of the status,\n * as well as used as keys in the state for tracking which elements are in which status(es)\n * @static\n * @type {{verbs: String[], adjectives: String[]}}\n * @private\n */\nexport const STATUSES = {\n verbs: ['highlight', 'select', 'fade', 'hide'],\n adjectives: ['highlighted', 'selected', 'faded', 'hidden'],\n};\n","/**\n * Transformation functions: used to transform a raw data value. For example, a template or axis label\n * can convert from pvalue to -log10pvalue by specifying the following field name (the `|funcname` syntax\n * indicates applying a function):\n *\n * `{{assoc:pvalue|neglog10}}`\n *\n * Transforms can also be chained so that several are used in order from left to right:\n * `{{log_pvalue|logtoscinotation|htmlescape}}`\n *\n * Most parts of LocusZoom that rely on being given a field name (or value) can be used this way: axis labels, position,\n * match/filter logic, tooltip HTML template, etc. If your use case is not working with filters, please file a\n * bug report!\n *\n * NOTE: for best results, don't specify filters in the `fields` array of a data layer- only specify them where the\n * transformed value will be used.\n * @module LocusZoom_TransformationFunctions\n */\n\n/**\n * Return the log10 of a value. Can be applied several times in a row for, eg, loglog plots.\n * @param {number} value\n * @return {null|number}\n */\nexport function log10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return Math.log(value) / Math.LN10;\n}\n\n/**\n * Return the -log (base 10), a common means of representing pvalues in locuszoom plots\n * @param {number} value\n * @return {number}\n */\nexport function neglog10 (value) {\n if (isNaN(value) || value <= 0) {\n return null;\n }\n return -Math.log(value) / Math.LN10;\n}\n\n/**\n * Convert a number from logarithm to scientific notation. Useful for, eg, a datasource that returns -log(p) by default\n * @param {number} value\n * @return {string}\n */\nexport function logtoscinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '1';\n }\n const exp = Math.ceil(value);\n const diff = exp - value;\n const base = Math.pow(10, diff);\n if (exp === 1) {\n return (base / 10).toFixed(4);\n } else if (exp === 2) {\n return (base / 100).toFixed(3);\n } else {\n return `${base.toFixed(2)} × 10^-${exp}`;\n }\n}\n\n/**\n * Represent a number in scientific notation\n * @param {Number} value\n * @returns {String}\n */\nexport function scinotation (value) {\n if (isNaN(value)) {\n return 'NaN';\n }\n if (value === 0) {\n return '0';\n }\n\n const abs = Math.abs(value);\n let log;\n if (abs > 1) {\n log = Math.ceil(Math.log(abs) / Math.LN10);\n } else { // 0...1\n log = Math.floor(Math.log(abs) / Math.LN10);\n }\n if (Math.abs(log) <= 3) {\n return value.toFixed(3);\n } else {\n return value.toExponential(2).replace('+', '').replace('e', ' × 10^');\n }\n}\n\n/**\n * HTML-escape user entered values for use in constructed HTML fragments\n *\n * For example, this filter can be used on tooltips with custom HTML display. This protects against some forms of\n * XSS injection when plotting user-provided data, as well as display artifacts from field values with HTML symbols\n * such as `<` or `>`.\n * @param {String} value HTML-escape the provided value\n * @return {string}\n */\nexport function htmlescape (value) {\n if (!value) {\n return '';\n }\n value = `${value}`;\n\n return value.replace(/['\"<>&`]/g, function (s) {\n switch (s) {\n case \"'\":\n return ''';\n case '\"':\n return '"';\n case '<':\n return '<';\n case '>':\n return '>';\n case '&':\n return '&';\n case '`':\n return '`';\n }\n });\n}\n\n/**\n * Return true if the value is numeric (including 0)\n *\n * This is useful in template code, where we might wish to hide a field that is absent, but show numeric values even if they are 0\n * Eg, `{{#if value|is_numeric}}...{{/if}}\n *\n * @param {Number} value\n * @return {boolean}\n */\nexport function is_numeric(value) {\n return typeof value === 'number';\n}\n\n/**\n * URL-encode the provided text, eg for constructing hyperlinks\n * @param {String} value\n * @return {string}\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","import {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values to control how values are rendered.\n * Provides syntactic sugar atop a standard registry.\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass TransformationFunctionsRegistry extends RegistryBase {\n /**\n * Helper function that turns a sequence of function names into a single callable\n * @param template_string\n * @return {function(*=): *}\n * @private\n */\n _collectTransforms(template_string) {\n const funcs = template_string\n .match(/\\|([^|]+)/g)\n .map((item) => super.get(item.substring(1)));\n\n return (value) => {\n return funcs.reduce(\n (acc, func) => func(acc),\n value,\n );\n };\n }\n\n /**\n * In templates, we often use a single concatenated string to ask for several transformation functions at once:\n * `value|func1|func2`\n * This class offers syntactical sugar to retrieve the entire sequence of transformations as a single callable\n * @param name\n */\n get(name) {\n if (!name) {\n // This function is sometimes called with no value, and the expected behavior is to return null instead of\n // a callable\n return null;\n }\n if (name.substring(0, 1) === '|') {\n // Legacy artifact of how this function is called- if a pipe is present, this is the template string\n // (`|func1|func2...`), rather than any one single transformation function.\n // A sequence of transformation functions is expected\n return this._collectTransforms(name);\n } else {\n // If not a template string, then user is asking for an item by name directly\n return super.get(name);\n }\n }\n}\n\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided transformation functions, which\n * can be used to modify a value in the input data in a predefined way. For example, these can be used to let APIs\n * that return p_values work with plots that display -log10(p)\n * @alias module:LocusZoom~TransformationFunctions\n * @type {TransformationFunctionsRegistry}\n */\nconst registry = new TransformationFunctionsRegistry();\nfor (let [name, type] of Object.entries(transforms)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n// Export helper class for unit testing\nexport { TransformationFunctionsRegistry as _TransformationFunctions };\n","import TRANSFORMS from '../registry/transforms';\n\n/**\n * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.\n *\n * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the\n * state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.\n *\n * @private\n * @class\n * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch\n * from that datasource, and arbitrarily many transformations to apply to the value. The namespace and\n * transformation(s) are optional and information is delimited according to the general syntax\n * `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`\n */\nclass Field {\n constructor(field) {\n // Two scenarios: we are requesting a field by full name, OR there are transforms to apply\n // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`\n const field_pattern = /^(?:\\w+:\\w+|^\\w+)(?:\\|\\w+)*$/;\n if (!field_pattern.test(field)) {\n throw new Error(`Invalid field specifier: '${field}'`);\n }\n\n const [name, ...transforms] = field.split('|');\n\n this.full_name = field; // fieldname + transforms\n this.field_name = name; // just fieldname\n this.transformations = transforms.map((name) => TRANSFORMS.get(name));\n }\n\n _applyTransformations(val) {\n this.transformations.forEach(function(transform) {\n val = transform(val);\n });\n return val;\n }\n\n /**\n * Resolve the field for a given data element.\n * First look for a full match with transformations already applied by the data requester.\n * Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.\n * @param {Object} data Returned data/fields into for this element\n * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want\n * to preserve across re-renders). Example usage: \"should_show_label\"\n * @returns {*}\n */\n resolve(data, extra) {\n // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null\n if (typeof data[this.full_name] == 'undefined') { // Check for cached result\n let val = null;\n if (data[this.field_name] !== undefined) { // Fallback: value sans transforms\n val = data[this.field_name];\n } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations\n val = extra[this.field_name];\n } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)\n data[this.full_name] = this._applyTransformations(val);\n }\n return data[this.full_name];\n }\n}\n\nexport {Field as default};\n","/**\n * Simplified JSONPath implementation\n *\n * This is designed to make it easier to modify part of a LocusZoom layout, using a syntax based on intent\n * (\"modify association panels\") rather than hard-coded assumptions (\"modify the first button, and gosh I hope the order doesn't change\")\n *\n * This DOES NOT support the full JSONPath specification. Notable limitations:\n * - Arrays can only be indexed by filter expression, not by number (can't ask for \"array item 1\")\n * - Filter expressions support only exact match, `field === value`. There is no support for \"and\" statements or\n * arbitrary JS expressions beyond a single exact comparison. (the parser may be improved in the future if use cases emerge)\n *\n * @module\n * @private\n */\n\nconst ATTR_REGEX = /^(\\*|[\\w]+)/; // attribute names can be wildcard or valid variable names\nconst EXPR_REGEX = /^\\[\\?\\(@((?:\\.[\\w]+)+) *===? *([0-9.eE-]+|\"[^\"]*\"|'[^']*')\\)\\]/; // Arrays can be indexed using filter expressions like `[?(@.id === value)]` where value is a number or a single-or-double quoted string\n\nfunction get_next_token(q) {\n // This just grabs everything that looks good.\n // The caller should check that the remaining query is valid.\n if (q.substr(0, 2) === '..') {\n if (q[2] === '[') {\n return {\n text: '..',\n attr: '*',\n depth: '..',\n };\n }\n const m = ATTR_REGEX.exec(q.substr(2));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dotdot_attr.`;\n }\n return {\n text: `..${m[0]}`,\n attr: m[1],\n depth: '..',\n };\n } else if (q[0] === '.') {\n const m = ATTR_REGEX.exec(q.substr(1));\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as dot_attr.`;\n }\n return {\n text: `.${m[0]}`,\n attr: m[1],\n depth: '.',\n };\n } else if (q[0] === '[') {\n const m = EXPR_REGEX.exec(q);\n if (!m) {\n throw `Cannot parse ${JSON.stringify(q)} as expr.`;\n }\n let value;\n try {\n // Parse strings and numbers\n value = JSON.parse(m[2]);\n } catch (e) {\n // Handle single-quoted strings\n value = JSON.parse(m[2].replace(/^'|'$/g, '\"'));\n }\n\n return {\n text: m[0],\n attrs: m[1].substr(1).split('.'),\n value,\n };\n } else {\n throw `The query ${JSON.stringify(q)} doesn't look valid.`;\n }\n}\n\nfunction normalize_query(q) {\n // Normalize the start of the query so that it's just a bunch of selectors one-after-another.\n // Otherwise the first selector is a little different than the others.\n if (!q) {\n return '';\n }\n if (!['$', '['].includes(q[0])) {\n q = `$.${ q}`;\n } // It starts with a dotless attr, so prepend the implied `$.`.\n if (q[0] === '$') {\n q = q.substr(1);\n } // strip the leading $\n return q;\n}\n\nfunction tokenize (q) {\n q = normalize_query(q);\n let selectors = [];\n while (q.length) {\n const selector = get_next_token(q);\n q = q.substr(selector.text.length);\n selectors.push(selector);\n }\n return selectors;\n}\n\n/**\n * Fetch the attribute from a dotted path inside a nested object, eg `extract_path({k:['a','b']}, ['k', 1])` would retrieve `'b'`\n *\n * This function returns a three item array `[parent, key, object]`. This is done to support mutating the value, which requires access to the parent.\n *\n * @param obj\n * @param path\n * @returns {Array}\n */\nfunction get_item_at_deep_path(obj, path) {\n let parent;\n for (let key of path) {\n parent = obj;\n obj = obj[key];\n }\n return [parent, path[path.length - 1], obj];\n}\n\nfunction tokens_to_keys(data, selectors) {\n // Resolve the jsonpath query into full path specifier keys in the object, eg\n // `$..data_layers[?(@.tag === 'association)].color\n // would become\n // [\"panels\", 0, \"data_layers\", 1, \"color\"]\n if (!selectors.length) {\n return [[]];\n }\n const sel = selectors[0];\n const remaining_selectors = selectors.slice(1);\n let paths = [];\n\n if (sel.attr && sel.depth === '.' && sel.attr !== '*') { // .attr\n const d = data[sel.attr];\n if (selectors.length === 1) {\n if (d !== undefined) {\n paths.push([sel.attr]);\n }\n } else {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n } else if (sel.attr && sel.depth === '.' && sel.attr === '*') { // .*\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n } else if (sel.attr && sel.depth === '..') { // ..\n // If `sel.attr` matches, recurse with that match.\n // And also recurse on every value using unchanged selectors.\n // I bet `..*..*` duplicates results, so don't do it please.\n if (typeof data === 'object' && data !== null) {\n if (sel.attr !== '*' && sel.attr in data) { // Exact match!\n paths.push(...tokens_to_keys(data[sel.attr], remaining_selectors).map((p) => [sel.attr].concat(p)));\n }\n for (let [k, d] of Object.entries(data)) {\n paths.push(...tokens_to_keys(d, selectors).map((p) => [k].concat(p))); // No match, just recurse\n if (sel.attr === '*') { // Wildcard match\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n } else if (sel.attrs) { // [?(@.attr===value)]\n for (let [k, d] of Object.entries(data)) {\n const [_, __, subject] = get_item_at_deep_path(d, sel.attrs);\n if (subject === sel.value) {\n paths.push(...tokens_to_keys(d, remaining_selectors).map((p) => [k].concat(p)));\n }\n }\n }\n\n const uniqPaths = uniqBy(paths, JSON.stringify); // dedup\n uniqPaths.sort((a, b) => b.length - a.length || JSON.stringify(a).localeCompare(JSON.stringify(b))); // sort longest-to-shortest, breaking ties lexicographically\n return uniqPaths;\n}\n\nfunction uniqBy(arr, key) {\n // Sometimes, the process of resolving paths to selectors returns duplicate results. This returns only the unique paths.\n return [...new Map(arr.map((elem) => [key(elem), elem])).values()];\n}\n\nfunction get_items_from_tokens(data, selectors) {\n let items = [];\n for (let path of tokens_to_keys(data, selectors)) {\n items.push(get_item_at_deep_path(data, path));\n }\n return items;\n}\n\n/**\n * Perform a query, and return the item + its parent context\n * @param data\n * @param query\n * @returns {Array}\n * @private\n */\nfunction _query(data, query) {\n const tokens = tokenize(query);\n\n const matches = get_items_from_tokens(data, tokens);\n if (!matches.length) {\n console.warn(`No items matched the specified query: '${query}'`);\n }\n return matches;\n}\n\n/**\n * Fetch the value(s) for each possible match for a given query. Returns only the item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @returns {Array}\n */\nfunction query(data, query) {\n return _query(data, query).map((item) => item[2]);\n}\n\n/**\n * Modify the value(s) for each possible match for a given jsonpath query. Returns the new item values.\n * @param {object} data The data object to query\n * @param {string} query A JSONPath-compliant query string\n * @param {function|*} value_or_callback The new value for the specified field. Mutations will only be applied\n * after the keys are resolved; this prevents infinite recursion, but could invalidate some matches\n * (if the mutation removed the expected key).\n */\nfunction mutate(data, query, value_or_callback) {\n const matches_in_context = _query(data, query);\n return matches_in_context.map(([parent, key, old_value]) => {\n const new_value = (typeof value_or_callback === 'function') ? value_or_callback(old_value) : value_or_callback;\n parent[key] = new_value;\n return new_value;\n });\n}\n\nexport {mutate, query};\n","/**\n * Utilities for modifying or working with layout objects\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport {mutate, query} from './jsonpath';\n\nconst sqrt3 = Math.sqrt(3);\n// D3 v5 does not provide a triangle down symbol shape, but it is very useful for showing direction of effect.\n// Modified from https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js\nconst triangledown = {\n draw(context, size) {\n const y = -Math.sqrt(size / (sqrt3 * 3));\n context.moveTo(0, -y * 2);\n context.lineTo(-sqrt3 * y, y);\n context.lineTo(sqrt3 * y, y);\n context.closePath();\n },\n};\n\n/**\n * Apply shared namespaces to a layout, recursively.\n *\n * Overriding namespaces can be used to identify where to retrieve data from, but not to add new data sources to a layout.\n * For that, a key would have to be added to `layout.namespace` directly.\n *\n * Detail: This function is a bit magical. Whereas most layouts overrides are copied over verbatim (merging objects and nested keys), applyNamespaces will *only* copy\n * over keys that are relevant to that data layer. Eg, if overrides specifies a key called \"red_herring\",\n * an association data layer will ignore that data source entirely, and not copy it into `assoc_layer.namespace`.\n *\n * Only items under a key called `namespace` are given this special treatment. This allows a single call to `Layouts.get('plot'...)` to specify\n * namespace overrides for many layers in one convenient place, without accidentally telling every layer to request all possible data for itself.\n * @private\n */\nfunction applyNamespaces(layout, shared_namespaces) {\n shared_namespaces = shared_namespaces || {};\n if (!layout || typeof layout !== 'object' || typeof shared_namespaces !== 'object') {\n throw new Error('Layout and shared namespaces must be provided as objects');\n }\n\n for (let [field_name, item] of Object.entries(layout)) {\n if (field_name === 'namespace') {\n Object.keys(item).forEach((requested_ns) => {\n const override = shared_namespaces[requested_ns];\n if (override) {\n item[requested_ns] = override;\n }\n });\n } else if (item !== null && (typeof item === 'object')) {\n layout[field_name] = applyNamespaces(item, shared_namespaces);\n }\n }\n return layout;\n}\n\n/**\n * A helper method used for merging two objects. If a key is present in both, takes the value from the first object.\n * Values from `default_layout` will be cleanly copied over, ensuring no references or shared state.\n *\n * Frequently used for preparing custom layouts. Both objects should be JSON-serializable.\n *\n * @alias LayoutRegistry.merge\n * @param {object} custom_layout An object containing configuration parameters that override or add to defaults\n * @param {object} default_layout An object containing default settings.\n * @returns {object} The custom layout is modified in place and also returned from this method.\n */\nfunction merge(custom_layout, default_layout) {\n if (typeof custom_layout !== 'object' || typeof default_layout !== 'object') {\n throw new Error(`LocusZoom.Layouts.merge only accepts two layout objects; ${typeof custom_layout}, ${typeof default_layout} given`);\n }\n for (let property in default_layout) {\n if (!Object.prototype.hasOwnProperty.call(default_layout, property)) {\n continue;\n }\n // Get types for comparison. Treat nulls in the custom layout as undefined for simplicity.\n // (javascript treats nulls as \"object\" when we just want to overwrite them as if they're undefined)\n // Also separate arrays from objects as a discrete type.\n let custom_type = custom_layout[property] === null ? 'undefined' : typeof custom_layout[property];\n let default_type = typeof default_layout[property];\n if (custom_type === 'object' && Array.isArray(custom_layout[property])) {\n custom_type = 'array';\n }\n if (default_type === 'object' && Array.isArray(default_layout[property])) {\n default_type = 'array';\n }\n // Unsupported property types: throw an exception\n if (custom_type === 'function' || default_type === 'function') {\n throw new Error('LocusZoom.Layouts.merge encountered an unsupported property type');\n }\n // Undefined custom value: pull the default value\n if (custom_type === 'undefined') {\n custom_layout[property] = deepCopy(default_layout[property]);\n continue;\n }\n // Both values are objects: merge recursively\n if (custom_type === 'object' && default_type === 'object') {\n custom_layout[property] = merge(custom_layout[property], default_layout[property]);\n continue;\n }\n }\n return custom_layout;\n}\n\nfunction deepCopy(item) {\n // FIXME: initial attempt to replace this with a more efficient deep clone method caused merge() to break; revisit in future.\n // Replacing this with a proper clone would be the key blocker to allowing functions and non-JSON values (like infinity) in layout objects\n return JSON.parse(JSON.stringify(item));\n}\n\n/**\n * Convert name to symbol\n * Layout objects accept symbol names as strings (circle, triangle, etc). Convert to symbol objects.\n * @return {object|null} An object that implements a draw method (eg d3-shape symbols or extra LZ items)\n */\nfunction nameToSymbol(shape) {\n if (!shape) {\n return null;\n }\n if (shape === 'triangledown') {\n // D3 does not provide this symbol natively\n return triangledown;\n }\n // Legend shape names are strings; need to connect this to factory. Eg circle --> d3.symbolCircle\n const factory_name = `symbol${shape.charAt(0).toUpperCase() + shape.slice(1)}`;\n return d3[factory_name] || null;\n}\n\n\n/**\n * Find all references to namespaced fields within a layout object. This is used to validate that a set of provided\n * data adapters will actually give all the information required to draw the plot.\n * @param {Object} layout\n * @param {Array|null} prefixes A list of allowed namespace prefixes. (used to differentiate between real fields,\n * and random sentences that match an arbitrary pattern.\n * @param {RegExp|null} field_finder On recursive calls, pass the regexp we constructed the first time\n * @return {Set}\n */\nfunction findFields(layout, prefixes, field_finder = null) {\n const fields = new Set();\n if (!field_finder) {\n if (!prefixes.length) {\n // A layer that doesn't ask for external data does not need to check if the provider returns expected fields\n return fields;\n }\n const all_ns = prefixes.join('|');\n\n // Locates any reference within a template string to to `ns:field`, `{{ns:field}}`, or `{{#if ns:field}}`.\n // By knowing the list of allowed NS prefixes, we can be much more confident in avoiding spurious matches\n field_finder = new RegExp(`(?:{{)?(?:#if *)?((?:${all_ns}):\\\\w+)`, 'g');\n }\n\n for (const value of Object.values(layout)) {\n const value_type = typeof value;\n let matches = [];\n if (value_type === 'string') {\n let a_match;\n while ((a_match = field_finder.exec(value)) !== null) {\n matches.push(a_match[1]);\n }\n } else if (value !== null && value_type === 'object') {\n matches = findFields(value, prefixes, field_finder);\n } else {\n // Only look for field names in strings or compound values\n continue;\n }\n for (let m of matches) {\n fields.add(m);\n }\n }\n return fields;\n}\n\n\n/**\n * A utility helper for customizing one part of a pre-made layout. Whenever a primitive value is found (eg string),\n * replaces *exact match*\n *\n * This method works by comparing whether strings are a match. As a result, the \"old\" and \"new\" names must match\n * whatever namespacing is used in the input layout.\n * Note: this utility *can* replace values with filters, but will not do so by default.\n *\n * @alias LayoutRegistry.renameField\n *\n * @param {object} layout The layout object to be transformed.\n * @param {string} old_name The old field name that will be replaced\n * @param {string} new_name The new field name that will be substituted in\n * @param {boolean} [warn_transforms=true] Sometimes, a field name is used with transforms appended, eg `label|htmlescape`.\n * In some cases a rename could change the meaning of the field, and by default this method will print a warning to\n * the console, encouraging the developer to check the relevant usages. This warning can be silenced via an optional function argument.\n */\nfunction renameField(layout, old_name, new_name, warn_transforms = true) {\n const this_type = typeof layout;\n // Handle nested types by recursion (in which case, `layout` may be something other than an object)\n if (Array.isArray(layout)) {\n return layout.map((item) => renameField(item, old_name, new_name, warn_transforms));\n } else if (this_type === 'object' && layout !== null) {\n return Object.keys(layout).reduce(\n (acc, key) => {\n acc[key] = renameField(layout[key], old_name, new_name, warn_transforms);\n return acc;\n }, {},\n );\n } else if (this_type !== 'string') {\n // Field names are always strings. If the value isn't a string, don't even try to change it.\n return layout;\n } else {\n // If we encounter a field we are trying to rename, then do so!\n // Rules:\n // 1. Try to avoid renaming part of a field, by checking token boundaries (field1 should not rename field1_displayvalue)\n // 2. Warn the user if filter functions are being used with the specified field, so they can audit for changes in meaning\n const escaped = old_name.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');\n\n if (warn_transforms) {\n // Warn the user that they might be renaming, eg, `pvalue|neg_log` to `log_pvalue|neg_log`. Let them decide\n // whether the new field name has a meaning that is compatible with the specified transforms.\n const filter_regex = new RegExp(`${escaped}\\\\|\\\\w+`, 'g');\n const filter_matches = (layout.match(filter_regex) || []);\n filter_matches.forEach((match_val) => console.warn(`renameFields is renaming a field that uses transform functions: was '${match_val}' . Verify that these transforms are still appropriate.`));\n }\n\n // Find and replace any substring, so long as it is at the end of a valid token\n const regex = new RegExp(`${escaped}(?!\\\\w+)`, 'g');\n return layout.replace(regex, new_name);\n }\n}\n\n/**\n * Modify any and all attributes at the specified path in the object\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which field(s) to change.\n * The callback will be applied to ALL matching selectors\n * (see Interactivity guide for syntax and limitations)\n * @param {*|function} value_or_callable The new value, or a function that receives the old value and returns a new one\n * @returns {Array}\n * @alias LayoutRegistry.mutate_attrs\n */\nfunction mutate_attrs(layout, selector, value_or_callable) {\n return mutate(\n layout,\n selector,\n value_or_callable,\n );\n}\n\n/**\n * Query any and all attributes at the specified path in the object.\n * This is mostly only useful for debugging, to verify that a particular selector matches the intended field.\n * @param {object} layout The layout object to be mutated\n * @param {string} selector The JSONPath-compliant selector string specifying which values to return. (see Interactivity guide for limits)\n * @returns {Array}\n * @alias LayoutRegistry.query_attrs\n */\nfunction query_attrs(layout, selector) {\n return query(layout, selector);\n}\n\nexport { applyNamespaces, deepCopy, merge, mutate_attrs, query_attrs, nameToSymbol, findFields, renameField };\n","/**\n * @module\n * @private\n */\nimport {getLinkedData} from './undercomplicate';\n\nimport { DATA_OPS } from '../registry';\n\n\nclass DataOperation {\n /**\n * Perform a data operation (such as a join)\n * @param {String} join_type\n * @param initiator The entity that initiated the request for data. Usually, this is the data layer. This argument exists so that a data_operation could do things like auto-define axis labels/ color scheme in response to dynamic data. It has potential for side effects if misused, so use sparingly!\n * @param params Optional user/layout parameters to be passed to the data function\n */\n constructor(join_type, initiator, params) {\n this._callable = DATA_OPS.get(join_type);\n this._initiator = initiator;\n this._params = params || [];\n }\n\n getData(plot_state, ...dependent_recordsets) {\n // Most operations are joins: they receive two pieces of data (eg left + right)\n // Other ops are possible, like consolidating just one set of records to best value per key\n // Hence all dependencies are passed as first arg: [dep1, dep2, dep3...]\n\n // Every data operation receives plot_state, reference to the data layer that called it, the input data, & any additional options\n const context = {plot_state, data_layer: this._initiator};\n return Promise.resolve(this._callable(context, dependent_recordsets, ...this._params));\n }\n}\n\n\n/**\n * The Requester manages fetching of data across multiple data sources. It is used internally by LocusZoom data layers.\n * It passes plot.state information to each adapter, and ensures that a series of requests can be performed in a\n * designated order.\n *\n * Each data layer calls the requester object directly, and as such, each data layer has a private view of data: it can\n * perform its own calculations, filter results, and apply transforms without influencing other layers.\n * (while still respecting a shared cache where appropriate)\n *\n * This object is not part of the public interface. It should almost **never** be replaced or modified directly.\n *\n * @param {DataSources} sources A set of data sources used specifically by this plot instance\n * @private\n */\nclass Requester {\n constructor(sources) {\n this._sources = sources;\n }\n\n /**\n * Parse the data layer configuration when a layer is first created.\n * Validate config, and return entities and dependencies in a format usable for data retrieval.\n * This is used by data layers, and also other data-retrieval functions (like subscribeToDate).\n *\n * Inherent assumptions:\n * 1. A data layer will always know its data up front, and layout mutations will only affect what is displayed.\n * 2. People will be able to add new data adapters (tracks), but if they are removed, the accompanying layers will be\n * removed at the same time. Otherwise, the pre-parsed data fetching logic could could preserve a reference to the\n * removed adapter.\n * @param {Object} namespace_options\n * @param {Array} data_operations\n * @param {Object|null} initiator The entity that initiated the request (the data layer). Passed to data operations,\n * but not adapters. By baking this reference into each data operation, functions can do things like autogenerate\n * axis tick marks or color schemes based on dyanmic data. This is an advanced usage and should be handled with care!\n * @returns {Array} Map of entities and list of dependencies\n */\n config_to_sources(namespace_options = {}, data_operations = [], initiator) {\n const entities = new Map();\n const namespace_local_names = Object.keys(namespace_options);\n\n // 1. Specify how to coordinate data. Precedence:\n // a) EXPLICIT fetch logic,\n // b) IMPLICIT auto-generate fetch order if there is only one NS,\n // c) Throw \"spec required\" error if > 1, because 2 adapters may need to be fetched in a sequence\n let dependency_order = data_operations.find((item) => item.type === 'fetch'); // explicit spec: {fetch, from}\n if (!dependency_order) {\n dependency_order = { type: 'fetch', from: namespace_local_names };\n data_operations.unshift(dependency_order);\n }\n\n // Validate that all NS items are available to the root requester in DataSources. All layers recognize a\n // default value, eg people copying the examples tend to have defined a datasource called \"assoc\"\n const ns_pattern = /^\\w+$/;\n for (let [local_name, global_name] of Object.entries(namespace_options)) {\n if (!ns_pattern.test(local_name)) {\n throw new Error(`Invalid namespace name: '${local_name}'. Must contain only alphanumeric characters`);\n }\n\n const source = this._sources.get(global_name);\n if (!source) {\n throw new Error(`A data layer has requested an item not found in DataSources: data type '${local_name}' from ${global_name}`);\n }\n entities.set(local_name, source);\n\n // Note: Dependency spec checker will consider \"ld(assoc)\" to match a namespace called \"ld\"\n if (!dependency_order.from.find((dep_spec) => dep_spec.split('(')[0] === local_name)) {\n // Sometimes, a new piece of data (namespace) will be added to a layer. Often this doesn't have any dependencies, other than adding a new join.\n // To make it easier to EXTEND existing layers, by default, we'll push any unknown namespaces to data_ops.fetch\n // Thus the default behavior is \"fetch all namespaces as though they don't depend on anything.\n // If they depend on something, only then does \"data_ops[@type=fetch].from\" need to be mutated\n dependency_order.from.push(local_name);\n }\n }\n\n let dependencies = Array.from(dependency_order.from);\n\n // Now check all joins. Are namespaces valid? Are they requesting known data?\n for (let config of data_operations) {\n let {type, name, requires, params} = config;\n if (type !== 'fetch') {\n let namecount = 0;\n if (!name) {\n name = config.name = `join${namecount}`;\n namecount += 1;\n }\n\n if (entities.has(name)) {\n throw new Error(`Configuration error: within a layer, join name '${name}' must be unique`);\n }\n requires.forEach((require_name) => {\n if (!entities.has(require_name)) {\n throw new Error(`Data operation cannot operate on unknown provider '${require_name}'`);\n }\n });\n\n const task = new DataOperation(type, initiator, params);\n entities.set(name, task);\n dependencies.push(`${name}(${requires.join(', ')})`); // Dependency resolver uses the form item(depA, depB)\n }\n }\n return [entities, dependencies];\n }\n\n /**\n * @param {Object} plot_state Plot state, which will be passed to every adapter. Includes view extent (chr, start, end)\n * @param {Map} entities A list of adapter and join tasks. This is created internally from data layer layouts.\n * Keys are layer-local namespaces for data types (like assoc), and values are adapter or join task instances\n * (things that implement a method getData).\n * @param {String[]} dependencies Instructions on what adapters to fetch from, in what order\n * @returns {Promise}\n */\n getData(plot_state, entities, dependencies) {\n if (!dependencies.length) {\n return Promise.resolve([]);\n }\n // The last dependency (usually the last join operation) determines the last thing returned.\n return getLinkedData(plot_state, entities, dependencies, true);\n }\n}\n\n\nexport default Requester;\n\nexport {DataOperation as _JoinTask};\n","/**\n * @module\n * @private\n */\n// FIXME: A place for code that used to live under the `LocusZoom` namespace\n// Eventually this should be moved into classes or some other mechanism for code sharing. No external uses should\n// depend on any items in this module.\n\nimport * as d3 from 'd3';\n\n/**\n * Generate a curtain object for a plot, panel, or any other subdivision of a layout\n * The panel curtain, like the plot curtain is an HTML overlay that obscures the entire panel. It can be styled\n * arbitrarily and display arbitrary messages. It is useful for reporting error messages visually to an end user\n * when the error renders the panel unusable.\n * TODO: Improve type doc here\n * @returns {object}\n */\nfunction generateCurtain() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n hide_delay: null,\n\n /**\n * Generate the curtain. Any content (string) argument passed will be displayed in the curtain as raw HTML.\n * CSS (object) can be passed which will apply styles to the curtain and its content.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n show: (content, css) => {\n if (!this.curtain.showing) {\n this.curtain.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-curtain')\n .attr('id', `${this.id}.curtain`);\n this.curtain.content_selector = this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-content');\n this.curtain.selector.append('div')\n .attr('class', 'lz-curtain-dismiss').html('Dismiss')\n .on('click', () => this.curtain.hide());\n this.curtain.showing = true;\n }\n return this.curtain.update(content, css);\n },\n\n /**\n * Update the content and css of the curtain that's currently being shown. This method also adjusts the size\n * and positioning of the curtain to ensure it still covers the entire panel with no overlap.\n * @param {string} content Content to be displayed on the curtain (as raw HTML)\n * @param {object} css Apply the specified styles to the curtain and its contents\n */\n update: (content, css) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n clearTimeout(this.curtain.hide_delay);\n // Apply CSS if provided\n if (typeof css == 'object') {\n applyStyles(this.curtain.selector, css);\n }\n // Update size and position\n const page_origin = this._getPageOrigin();\n\n // Panel layouts have a height; plot layouts don't\n const height = this.layout.height || this._total_height;\n this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.parent_plot.layout.width}px`)\n .style('height', `${height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${height - 40}px`);\n // Apply content if provided\n if (typeof content == 'string') {\n this.curtain.content_selector.html(content);\n }\n return this.curtain;\n },\n\n /**\n * Remove the curtain\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.curtain.showing) {\n return this.curtain;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.curtain.hide_delay);\n this.curtain.hide_delay = setTimeout(this.curtain.hide, delay);\n return this.curtain;\n }\n // Remove curtain\n this.curtain.selector.remove();\n this.curtain.selector = null;\n this.curtain.content_selector = null;\n this.curtain.showing = false;\n return this.curtain;\n },\n };\n}\n\n/**\n * Generate a loader object for a plot, panel, or any other subdivision of a layout\n *\n * The panel loader is a small HTML overlay that appears in the lower left corner of the panel. It cannot be styled\n * arbitrarily, but can show a custom message and show a minimalist loading bar that can be updated to specific\n * completion percentages or be animated.\n * TODO Improve type documentation\n * @returns {object}\n */\nfunction generateLoader() {\n return {\n showing: false,\n selector: null,\n content_selector: null,\n progress_selector: null,\n cancel_selector: null,\n\n /**\n * Show a loading indicator\n * @param {string} [content='Loading...'] Loading message (displayed as raw HTML)\n */\n show: (content) => {\n // Generate loader\n if (!this.loader.showing) {\n this.loader.selector = d3.select(this.parent_plot.svg.node().parentNode).insert('div')\n .attr('class', 'lz-loader')\n .attr('id', `${this.id}.loader`);\n this.loader.content_selector = this.loader.selector.append('div')\n .attr('class', 'lz-loader-content');\n this.loader.progress_selector = this.loader.selector\n .append('div')\n .attr('class', 'lz-loader-progress-container')\n .append('div')\n .attr('class', 'lz-loader-progress');\n\n this.loader.showing = true;\n if (typeof content == 'undefined') {\n content = 'Loading...';\n }\n }\n return this.loader.update(content);\n },\n\n /**\n * Update the currently displayed loader and ensure the new content is positioned correctly.\n * @param {string} content The text to display (as raw HTML). If not a string, will be ignored.\n * @param {number} [percent] A number from 1-100. If a value is specified, it will stop all animations\n * in progress.\n */\n update: (content, percent) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n clearTimeout(this.loader.hide_delay);\n // Apply content if provided\n if (typeof content == 'string') {\n this.loader.content_selector.html(content);\n }\n // Update size and position\n const padding = 6; // is there a better place to store/define this?\n const page_origin = this._getPageOrigin();\n const loader_boundrect = this.loader.selector.node().getBoundingClientRect();\n this.loader.selector\n .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`)\n .style('left', `${page_origin.x + padding }px`);\n\n // Apply percent if provided\n if (typeof percent == 'number') {\n this.loader.progress_selector\n .style('width', `${Math.min(Math.max(percent, 1), 100)}%`);\n }\n return this.loader;\n },\n\n /**\n * Adds a class to the loading bar that makes it loop infinitely in a loading animation. Useful when exact\n * percent progress is not available.\n */\n animate: () => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', true);\n return this.loader;\n },\n\n /**\n * Sets the loading bar in the loader to percentage width equal to the percent (number) value passed. Percents\n * will automatically be limited to a range of 1 to 100. Will stop all animations in progress.\n */\n setPercentCompleted: (percent) => {\n this.loader.progress_selector.classed('lz-loader-progress-animated', false);\n return this.loader.update(null, percent);\n },\n\n /**\n * Remove the loader\n * @param {number} delay Time to wait (in ms)\n */\n hide: (delay) => {\n if (!this.loader.showing) {\n return this.loader;\n }\n // If a delay was passed then defer to a timeout\n if (typeof delay == 'number') {\n clearTimeout(this.loader.hide_delay);\n this.loader.hide_delay = setTimeout(this.loader.hide, delay);\n return this.loader;\n }\n // Remove loader\n this.loader.selector.remove();\n this.loader.selector = null;\n this.loader.content_selector = null;\n this.loader.progress_selector = null;\n this.loader.cancel_selector = null;\n this.loader.showing = false;\n return this.loader;\n },\n };\n}\n\n/**\n * Modern d3 removed the ability to set many styles at once (object syntax). This is a helper so that layouts with\n * config-objects can set styles all at once\n * @private\n * @param {d3.selection} selection\n * @param {Object} styles\n */\nfunction applyStyles(selection, styles) {\n styles = styles || {};\n for (let [prop, value] of Object.entries(styles)) {\n selection.style(prop, value);\n }\n}\n\n/**\n * Prevent a UI function from being called more than once in a given interval. This allows, eg, search boxes to delay\n * expensive operations until the user is done typing\n * @param {function} func The function to debounce. Returns a wrapper.\n * @param {number} delay Time to wait after last call (in ms)\n */\nfunction debounce(func, delay = 500) {\n let timer;\n return () => {\n clearTimeout(timer);\n timer = setTimeout(\n () => func.apply(this, arguments),\n delay,\n );\n };\n}\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/**\n * Interactive toolbar widgets that allow users to control the plot. These can be used to modify element display:\n * adding contextual information, rearranging/removing panels, or toggling between sets of rendering options like\n * different LD populations.\n * @module LocusZoom_Widgets\n */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n\n/**\n *\n * A widget is an empty div rendered on a toolbar that can display custom\n * html of user interface elements.\n */\nclass BaseWidget {\n /**\n * @param {('left'|'right')} [layout.position='left'] Whether to float the widget left or right.\n * @param {('start'|'middle'|'end')} [layout.group_position] Buttons can optionally be gathered into a visually\n * distinctive group whose elements are closer together. If a button is identified as the start or end of a group,\n * it will be drawn with rounded corners and an extra margin of spacing from any button not part of the group.\n * For example, the region_nav_plot toolbar is a defined as a group.\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} [layout.color='gray'] Color scheme for the\n * widget. Applies to buttons and menus.\n * @param [layout.style] CSS styles that will be applied to the widget\n * @param {Toolbar} parent The toolbar that contains this widget\n */\n constructor(layout, parent) {\n /** @member {Object} */\n this.layout = layout || {};\n if (!this.layout.color) {\n this.layout.color = 'gray';\n }\n\n /** @member {Toolbar|*} */\n this.parent = parent || null;\n /**\n * Some widgets are attached to a panel, rather than directly to a plot\n * @member {Panel|null}\n */\n this.parent_panel = null;\n /** @member {Plot} */\n this.parent_plot = null;\n /**\n * This is a reference to either the panel or the plot, depending on what the toolbar is\n * tied to. Useful when absolutely positioning toolbar widgets relative to their SVG anchor.\n * @member {Plot|Panel}\n */\n this.parent_svg = null;\n if (this.parent) {\n if (this.parent.type === 'panel') {\n this.parent_panel = this.parent.parent;\n this.parent_plot = this.parent.parent.parent;\n this.parent_svg = this.parent_panel;\n } else {\n this.parent_plot = this.parent.parent;\n this.parent_svg = this.parent_plot;\n }\n }\n /** @member {d3.selection} */\n this.selector = null;\n /**\n * If this is an interactive widget, it will contain a button or menu instance that handles the interactivity.\n * There is a 1-to-1 relationship of toolbar widget to button\n * @member {null|Button}\n */\n this.button = null;\n /**\n * If any single widget is marked persistent, it will bubble up to prevent automatic hide behavior on a\n * widget's parent toolbar. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n if (!this.layout.position) {\n this.layout.position = 'left';\n }\n }\n\n /**\n * Perform all rendering of widget, including toggling visibility to true. Will initialize and create SVG element\n * if necessary, as well as updating with new data and performing layout actions.\n */\n show() {\n if (!this.parent || !this.parent.selector) {\n return;\n }\n if (!this.selector) {\n const group_position = (['start', 'middle', 'end'].includes(this.layout.group_position) ? ` lz-toolbar-group-${this.layout.group_position}` : '');\n this.selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-${this.layout.position}${group_position}`);\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n if (typeof this.initialize == 'function') {\n this.initialize();\n }\n }\n if (this.button && this.button.status === 'highlighted') {\n this.button.menu.show();\n }\n this.selector.style('visibility', 'visible');\n this.update();\n return this.position();\n }\n\n /**\n * Update the toolbar widget with any new data or plot state as appropriate. This method performs all\n * necessary rendering steps.\n */\n update() { /* stub */\n }\n\n /**\n * Place the widget correctly in the plot\n * @returns {BaseWidget}\n */\n position() {\n if (this.button) {\n this.button.menu.position();\n }\n return this;\n }\n\n /**\n * Determine whether the widget should persist (will bubble up to parent toolbar)\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n return !!(this.button && this.button.persist);\n }\n\n /**\n * Toggle visibility to hidden, unless marked as persistent\n * @returns {BaseWidget}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n if (this.button) {\n this.button.menu.hide();\n }\n this.selector.style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove widget and button. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n if (this.button && this.button.menu) {\n this.button.menu.destroy();\n }\n this.selector.remove();\n this.selector = null;\n this.button = null;\n return this;\n }\n}\n\n/**\n * Plots and panels may have a \"toolbar\" element suited for showing HTML widgets that may be interactive.\n * When widgets need to incorporate a generic button, or additionally a button that generates a menu, this\n * class provides much of the necessary framework. This widget is rarely used directly; it is usually used as\n * part of the code for other widgets.\n * @alias module:LocusZoom_Widgets~_Button\n * @param {BaseWidget} parent\n */\nclass Button {\n constructor(parent) {\n if (!(parent instanceof BaseWidget)) {\n throw new Error('Unable to create toolbar widget button, invalid parent');\n }\n /** @member {BaseWidget} */\n this.parent = parent;\n /** @member {Panel} */\n this.parent_panel = this.parent.parent_panel;\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n /** @member {Plot|Panel} */\n this.parent_svg = this.parent.parent_svg;\n\n /** @member {Toolbar|null|*} */\n this.parent_toolbar = this.parent.parent;\n /** @member {d3.selection} */\n this.selector = null;\n\n /**\n * Tag to use for the button (default: a)\n * @member {String}\n */\n this.tag = 'a';\n\n /**\n * HTML for the button to show.\n * @protected\n * @member {String}\n */\n this.html = '';\n\n /**\n * Mouseover title text for the button to show\n * @protected\n * @member {String}\n */\n this.title = '';\n\n /**\n * Color of the button\n * @member {String}\n */\n this.color = 'gray';\n\n /**\n * Hash of arbitrary button styles to apply as {name: value} entries\n * @protected\n * @member {Object}\n */\n this.style = {};\n\n // Permanence\n /**\n * Track internal state on whether to keep showing the button/ menu contents at the moment\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n /**\n * Configuration when defining a button: track whether this widget should be allowed to keep open\n * menu/button contents in response to certain events\n * @protected\n * @member {Boolean}\n */\n this.permanent = false;\n\n /**\n * Button status (highlighted / disabled/ etc)\n * @protected\n * @member {String}\n */\n this.status = '';\n\n /**\n * Button Menu Object\n * The menu is an HTML overlay that can appear below a button. It can contain arbitrary HTML and\n * has logic to be automatically positioned and sized to behave more or less like a dropdown menu.\n * @member {Object}\n */\n this.menu = {\n outer_selector: null,\n inner_selector: null,\n scroll_position: 0,\n hidden: true,\n /**\n * Show the button menu, including setting up any DOM elements needed for first rendering\n */\n show: () => {\n if (!this.menu.outer_selector) {\n this.menu.outer_selector = d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', `lz-toolbar-menu lz-toolbar-menu-${this.color}`)\n .attr('id', `${this.parent_svg.getBaseId()}.toolbar.menu`);\n this.menu.inner_selector = this.menu.outer_selector.append('div')\n .attr('class', 'lz-toolbar-menu-content');\n this.menu.inner_selector.on('scroll', () => {\n this.menu.scroll_position = this.menu.inner_selector.node().scrollTop;\n });\n }\n this.menu.outer_selector.style('visibility', 'visible');\n this.menu.hidden = false;\n return this.menu.update();\n },\n /**\n * Update the rendering of the menu\n */\n update: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.populate(); // This function is stubbed for all buttons by default and custom implemented in widget definition\n if (this.menu.inner_selector) {\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n }\n return this.menu.position();\n },\n position: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n // Unset any explicitly defined outer selector height so that menus dynamically shrink if content is removed\n this.menu.outer_selector.style('height', null);\n const padding = 3;\n const scrollbar_padding = 20;\n const menu_height_padding = 14; // 14: 2x 6px padding, 2x 1px border\n const page_origin = this.parent_svg._getPageOrigin();\n const page_scroll_top = document.documentElement.scrollTop || document.body.scrollTop;\n const container_offset = this.parent_plot.getContainerOffset();\n const toolbar_client_rect = this.parent_toolbar.selector.node().getBoundingClientRect();\n const button_client_rect = this.selector.node().getBoundingClientRect();\n const menu_client_rect = this.menu.outer_selector.node().getBoundingClientRect();\n const total_content_height = this.menu.inner_selector.node().scrollHeight;\n let top;\n let left;\n if (this.parent_toolbar.type === 'panel') {\n top = (page_origin.y + toolbar_client_rect.height + (2 * padding));\n left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding);\n } else {\n top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top;\n left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding);\n }\n const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding);\n const container_max_width = base_max_width;\n const content_max_width = (base_max_width - (4 * padding));\n const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding);\n const height = Math.min(total_content_height + menu_height_padding, base_max_height);\n this.menu.outer_selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('max-width', `${container_max_width}px`)\n .style('max-height', `${base_max_height}px`)\n .style('height', `${height}px`);\n this.menu.inner_selector\n .style('max-width', `${content_max_width}px`);\n this.menu.inner_selector.node().scrollTop = this.menu.scroll_position;\n return this.menu;\n },\n hide: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.outer_selector.style('visibility', 'hidden');\n this.menu.hidden = true;\n return this.menu;\n },\n destroy: () => {\n if (!this.menu.outer_selector) {\n return this.menu;\n }\n this.menu.inner_selector.remove();\n this.menu.outer_selector.remove();\n this.menu.inner_selector = null;\n this.menu.outer_selector = null;\n return this.menu;\n },\n /**\n * Internal method definition\n * By convention populate() does nothing and should be reimplemented with each toolbar button definition\n * Reimplement by way of Toolbar.BaseWidget.Button.menu.setPopulate to define the populate method and hook\n * up standard menu click-toggle behavior prototype.\n * @protected\n */\n populate: () => {\n throw new Error('Method must be implemented');\n },\n /**\n * Define how the menu is populated with items, and set up click and display properties as appropriate\n * @public\n */\n setPopulate: (menu_populate_function) => {\n if (typeof menu_populate_function == 'function') {\n this.menu.populate = menu_populate_function;\n this.setOnclick(() => {\n if (this.menu.hidden) {\n this.menu.show();\n this.highlight().update();\n this.persist = true;\n } else {\n this.menu.hide();\n this.highlight(false).update();\n if (!this.permanent) {\n this.persist = false;\n }\n }\n });\n } else {\n this.setOnclick();\n }\n return this;\n },\n };\n }\n\n /**\n * Set the color associated with this button\n * @param {('gray'|'red'|'orange'|'yellow'|'green'|'blue'|'purple')} color Any selection not in the preset list\n * will be replaced with gray.\n * @returns {Button}\n */\n setColor (color) {\n if (typeof color != 'undefined') {\n if (['gray', 'red', 'orange', 'yellow', 'green', 'blue', 'purple'].includes(color)) {\n this.color = color;\n } else {\n this.color = 'gray';\n }\n }\n return this;\n }\n\n /**\n * Allow code to change whether the button is allowed to be `permanent`\n * @param {boolean} bool\n * @returns {Button}\n */\n setPermanent (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n this.permanent = bool;\n if (this.permanent) {\n this.persist = true;\n }\n return this;\n }\n\n /**\n * Determine whether the button/menu contents should persist in response to a specific event\n * @returns {Boolean}\n */\n shouldPersist () {\n return this.permanent || this.persist;\n }\n\n /**\n * Set a collection of custom styles to be used by the button\n * @param {Object} style Hash of {name:value} entries\n * @returns {Button}\n */\n setStyle (style) {\n if (typeof style != 'undefined') {\n this.style = style;\n }\n return this;\n }\n\n /**\n * Method to generate a CSS class string\n * @returns {string}\n */\n getClass () {\n const group_position = (['start', 'middle', 'end'].includes(this.parent.layout.group_position) ? ` lz-toolbar-button-group-${this.parent.layout.group_position}` : '');\n return `lz-toolbar-button lz-toolbar-button-${this.color}${this.status ? `-${this.status}` : ''}${group_position}`;\n }\n\n /**\n * Change button state\n * @param {('highlighted'|'disabled'|'')} status\n */\n setStatus (status) {\n if (typeof status != 'undefined' && ['', 'highlighted', 'disabled'].includes(status)) {\n this.status = status;\n }\n return this.update();\n }\n\n /**\n * Toggle whether the button is highlighted\n * @param {boolean} bool If provided, explicitly set highlighted state\n * @returns {Button}\n */\n highlight (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('highlighted');\n } else if (this.status === 'highlighted') {\n return this.setStatus('');\n }\n return this;\n }\n\n /**\n * Toggle whether the button is disabled\n * @param {boolean} bool If provided, explicitly set disabled state\n * @returns {Button}\n */\n disable (bool) {\n if (typeof bool == 'undefined') {\n bool = true;\n } else {\n bool = Boolean(bool);\n }\n if (bool) {\n return this.setStatus('disabled');\n } else if (this.status === 'disabled') {\n return this.setStatus('');\n }\n return this;\n }\n\n // Mouse events\n /** @member {function} */\n onmouseover () {\n }\n setOnMouseover (onmouseover) {\n if (typeof onmouseover == 'function') {\n this.onmouseover = onmouseover;\n } else {\n this.onmouseover = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onmouseout () {\n }\n setOnMouseout (onmouseout) {\n if (typeof onmouseout == 'function') {\n this.onmouseout = onmouseout;\n } else {\n this.onmouseout = function () {};\n }\n return this;\n }\n\n /** @member {function} */\n onclick () {\n }\n setOnclick (onclick) {\n if (typeof onclick == 'function') {\n this.onclick = onclick;\n } else {\n this.onclick = function () {};\n }\n return this;\n }\n\n /**\n * Set the mouseover title text for the button (if any)\n * @param {String} title Simple text to display\n * @returns {Button}\n */\n setTitle(title) {\n if (typeof title != 'undefined') {\n this.title = title.toString();\n }\n return this;\n }\n\n /**\n * Specify the HTML content of this button.\n * WARNING: The string provided will be inserted into the document as raw markup; XSS mitigation is the\n * responsibility of each button implementation.\n * @param {String} html\n * @returns {Button}\n */\n setHtml(html) {\n if (typeof html != 'undefined') {\n this.html = html.toString();\n }\n return this;\n }\n\n // Primary behavior functions\n /**\n * Show the button, including creating DOM elements if necessary for first render\n */\n show () {\n if (!this.parent) {\n return;\n }\n if (!this.selector) {\n this.selector = this.parent.selector.append(this.tag)\n .attr('class', this.getClass());\n }\n return this.update();\n }\n\n /**\n * Hook for any actions or state cleanup to be performed before rerendering\n * @returns {Button}\n */\n preUpdate () {\n return this;\n }\n\n /**\n * Update button state and contents, and fully rerender\n * @returns {Button}\n */\n update () {\n if (!this.selector) {\n return this;\n }\n this.preUpdate();\n this.selector\n .attr('class', this.getClass())\n .attr('title', this.title)\n .on('mouseover', (this.status === 'disabled') ? null : this.onmouseover)\n .on('mouseout', (this.status === 'disabled') ? null : this.onmouseout)\n .on('click', (this.status === 'disabled') ? null : this.onclick)\n .html(this.html)\n .call(applyStyles, this.style);\n\n this.menu.update();\n this.postUpdate();\n return this;\n }\n\n /**\n * Hook for any behavior to be added/changed after the button has been re-rendered\n * @returns {Button}\n */\n postUpdate () {\n return this;\n }\n\n /**\n * Hide the button by removing it from the DOM (may be overridden by current persistence setting)\n * @returns {Button}\n */\n hide() {\n if (this.selector && !this.shouldPersist()) {\n this.selector.remove();\n this.selector = null;\n }\n return this;\n }\n\n}\n\n/**\n * Renders arbitrary text with large title formatting\n * @alias module:LocusZoom_Widgets~title\n * @param {string} layout.title Text or HTML to render\n * @param {string} [layout.subtitle] Small text to render next to the title\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass Title extends BaseWidget {\n show() {\n if (!this.div_selector) {\n this.div_selector = this.parent.selector.append('div')\n .attr('class', `lz-toolbar-title lz-toolbar-${this.layout.position}`);\n this.title_selector = this.div_selector.append('h3');\n }\n return this.update();\n }\n\n update() {\n let title = this.layout.title.toString();\n if (this.layout.subtitle) {\n title += ` ${this.layout.subtitle}`;\n }\n this.title_selector.html(title);\n return this;\n }\n}\n\n/**\n * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`. Few users are interested in seeing coordinates with this level of precision, but\n * it can be useful for debugging.\n * TODO: It would be nice to move this to an extension, but helper functions drag in large dependencies as a side effect.\n * (we'd need to reorganize internals a bit before moving this widget)\n * @alias module:LocusZoom_Widgets~region_scale\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RegionScale extends BaseWidget {\n update() {\n if (!isNaN(this.parent_plot.state.start) && !isNaN(this.parent_plot.state.end)\n && this.parent_plot.state.start !== null && this.parent_plot.state.end !== null) {\n this.selector.style('display', null);\n this.selector.html(positionIntToString(this.parent_plot.state.end - this.parent_plot.state.start, null, true));\n } else {\n this.selector.style('display', 'none');\n }\n if (this.layout.class) {\n this.selector.attr('class', this.layout.class);\n }\n if (this.layout.style) {\n applyStyles(this.selector, this.layout.style);\n }\n return this;\n }\n}\n\n/**\n * The filter field widget has triggered an update to the plot filtering rules\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_filter_field_action\n * @property {Object} data { field, operator, value, filter_id }\n * @see event:any_lz_event\n */\n\n/**\n * @alias module:LocusZoom_Widgets~filter_field\n */\nclass FilterField extends BaseWidget {\n /**\n * @param {string} layout.layer_name The data layer to control with filtering\n * @param {string} [layout.filter_id = null] Sometimes we want to define more than one filter with the same operator\n * (eg != null, != bacon). The `filter_id` option allows us to identify which filter is controlled by this widget.\n * @param {string} layout.field The field to be filtered (eg `assoc:log_pvalue`)\n * @param {string} layout.field_display_html Human-readable label for the field to be filtered (`-log10p`)\n * @param {string} layout.operator The operator to use when filtering. This must be one of the options allowed by data_layer.filter.\n * @param {number} [layout.input_size=4] How wide to make the input textbox (number characters shown at a time)\n * @param {('number'|'string')} [layout.data_type='number'] Convert the text box input to the specified type, and warn the\n * user if the value would be invalid (eg, not numeric)\n * @param {string} [layout.custom_event_name='widget_filter_field_action'] The name of the event that will be emitted when this filter is updated\n */\n constructor(layout, parent) {\n super(layout, parent);\n\n if (!this.parent_panel) {\n throw new Error('Filter widget can only be used in panel toolbars');\n }\n\n this._data_layer = this.parent_panel.data_layers[layout.layer_name];\n if (!this._data_layer) {\n throw new Error(`Filter widget could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n\n this._event_name = layout.custom_event_name || 'widget_filter_field_action';\n this._field = layout.field;\n this._field_display_html = layout.field_display_html;\n this._operator = layout.operator;\n this._filter_id = null;\n this._data_type = layout.data_type || 'number';\n if (!['number', 'string'].includes(this._data_type)) {\n throw new Error('Filter must be either string or number');\n }\n\n this._value_selector = null;\n }\n\n _getTarget() {\n // Find the specific filter in layer.layout.filters, and if not present, add one\n if (!this._data_layer.layout.filters) {\n this._data_layer.layout.filters = [];\n }\n let result = this._data_layer.layout.filters\n .find((item) => item.field === this._field && item.operator === this._operator && (!this._filter_id || item.id === this._filter_id));\n\n if (!result) {\n result = { field: this._field, operator: this._operator, value: null };\n if (this._filter_id) {\n result['id'] = this._filter_id;\n }\n this._data_layer.layout.filters.push(result);\n }\n return result;\n }\n\n /** Clear the filter by removing it from the list */\n _clearFilter() {\n if (this._data_layer.layout.filters) {\n const index = this._data_layer.layout.filters.indexOf(this._getTarget());\n this._data_layer.layout.filters.splice(index, 1);\n }\n }\n\n /**\n * Set the filter based on a provided value\n * @fires event:widget_filter_field_action\n */\n _setFilter(value) {\n if (value === null) {\n // On blank or invalid value, remove the filter & warn\n this._value_selector\n .style('border', '1px solid red')\n .style('color', 'red');\n this._clearFilter();\n } else {\n const filter = this._getTarget();\n filter.value = value;\n }\n this.parent_svg.emit(this._event_name, { field: this._field, operator: this._operator, value, filter_id: this._filter_id }, true);\n }\n\n /** Get the user-entered value, coercing type if necessary. Returns null for invalid or missing values.\n * @return {null|number|string}\n * @private\n */\n _getValue() {\n let value = this._value_selector.property('value');\n if (value === null || value === '') {\n return null;\n }\n if (this._data_type === 'number') {\n value = +value;\n if (Number.isNaN(value)) {\n return null;\n }\n }\n return value;\n }\n\n update() {\n if (this._value_selector) {\n return;\n }\n this.selector.style('padding', '0 6px');\n\n // Label\n this.selector\n .append('span')\n .html(this._field_display_html)\n .style('background', '#fff')\n .style('padding-left', '3px');\n // Operator label\n this.selector.append('span')\n .text(this._operator)\n .style('padding', '0 3px')\n .style('background', '#fff');\n\n this._value_selector = this.selector\n .append('input')\n .attr('size', this.layout.input_size || 4)\n .on('input', debounce(() => {\n // Clear validation state\n this._value_selector\n .style('border', null)\n .style('color', null);\n const value = this._getValue();\n this._setFilter(value);\n this.parent_panel.render();\n }, 750));\n }\n}\n\n/**\n * The user has asked to download the plot as an SVG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_svg\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * The user has asked to download the plot as a PNG image\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_save_png\n * @property {Object} data { filename }\n * @see event:any_lz_event\n */\n\n/**\n * Button to export current plot to an SVG image\n * @alias module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download hi-res image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_svg'] The name of the event that will be emitted when the button is clicked\n */\n constructor(layout, parent) {\n super(layout, parent);\n this._filename = this.layout.filename || 'locuszoom.svg';\n this._button_html = this.layout.button_html || 'Save SVG';\n this._button_title = this.layout.button_title || 'Download hi-res image';\n this._event_name = layout.custom_event_name || 'widget_save_svg';\n }\n\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this._button_html)\n .setTitle(this._button_title)\n .setOnMouseover(() => {\n this.button.selector\n .classed('lz-toolbar-button-gray-disabled', true)\n .html('Preparing Image');\n this._getBlobUrl().then((url) => {\n const old = this.button.selector.attr('href');\n if (old) {\n // Clean up old url instance to prevent memory leaks\n URL.revokeObjectURL(old);\n }\n this.button.selector\n .attr('href', url)\n .classed('lz-toolbar-button-gray-disabled', false)\n .classed('lz-toolbar-button-gray-highlighted', true)\n .html(this._button_html);\n });\n })\n .setOnMouseout(() => {\n this.button.selector.classed('lz-toolbar-button-gray-highlighted', false);\n });\n this.button.show();\n this.button.selector\n .attr('href-lang', 'image/svg+xml')\n .attr('download', this._filename)\n .on('click', () => this.parent_svg.emit(this._event_name, { filename: this._filename }, true));\n return this;\n }\n\n /**\n * Extract all CSS rules whose selectors directly reference elements under the root node\n * @param {Element} root\n * @return {string}\n * @private\n */\n _getCSS(root) {\n // Hack: this method is based on text matching the rules on a given node; it doesn't handle, eg ancestors.\n // Since all LZ cssRules are written as \"svg .classname\", we need to strip the parent selector prefix in order\n // to extract CSS.\n const ancestor_pattern = /^svg\\.lz-locuszoom\\s*/;\n\n // Extract all relevant CSS Rules by iterating through all available stylesheets\n let extractedCSSText = '';\n for (let i = 0; i < document.styleSheets.length; i++) {\n const s = document.styleSheets[i];\n try {\n if (!s.cssRules) {\n continue;\n }\n } catch ( e ) {\n if (e.name !== 'SecurityError') {\n throw e;\n } // for Firefox\n continue;\n }\n let cssRules = s.cssRules;\n for (let i = 0; i < cssRules.length; i++) {\n // FIXME: We could write smaller SVGs by extracting only the exact CSS rules for this plot. However,\n // extracting rules (including parent selectors) is a finicky process\n // Instead just fetch all LZ plot rules, under a known hardcoded parent selector.\n const rule = cssRules[i];\n const is_match = (rule.selectorText && rule.selectorText.match(ancestor_pattern));\n if (is_match) {\n extractedCSSText += rule.cssText;\n }\n }\n }\n return extractedCSSText;\n }\n\n _appendCSS( cssText, element ) {\n // Append styles to the constructed SVG DOM node\n var styleElement = document.createElement('style');\n styleElement.setAttribute('type', 'text/css');\n styleElement.innerHTML = cssText;\n var refNode = element.hasChildNodes() ? element.children[0] : null;\n element.insertBefore( styleElement, refNode );\n }\n\n /**\n * Get the target dimensions for the rendered image.\n *\n * For non-vector displays, these dimensions will yield ~300 DPI image for an 8\" wide print figure.\n * @return {number[]}\n * @private\n */\n _getDimensions() {\n let { width, height } = this.parent_plot.svg.node().getBoundingClientRect();\n const target_width = 2400;\n const rescale = target_width / width;\n return [rescale * width, rescale * height];\n }\n\n _generateSVG () {\n return new Promise((resolve) => {\n // Copy the DOM node so that we can modify the image for publication\n let copy = this.parent_plot.svg.node().cloneNode(true);\n copy.setAttribute('xlink', 'http://www.w3.org/1999/xlink');\n copy = d3.select(copy);\n\n // Remove unnecessary elements\n copy.selectAll('g.lz-curtain').remove();\n copy.selectAll('g.lz-mouse_guide').remove();\n // Convert units on axis tick dy attributes from ems to pixels\n copy.selectAll('g.tick text').each(function() {\n const dy = +(d3.select(this).attr('dy').substring(-2).slice(0, -2)) * 10;\n d3.select(this).attr('dy', dy);\n });\n // Pull the svg into a string and add the contents of the locuszoom stylesheet\n // Don't add this with d3 because it will escape the CDATA declaration incorrectly\n const serializer = new XMLSerializer();\n\n copy = copy.node();\n\n // Firefox has issues saving the SVG in certain contexts (esp rendering to canvas) unless a width is given.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=700533\n const [width, height] = this._getDimensions();\n copy.setAttribute('width', width);\n copy.setAttribute('height', height);\n\n // Add CSS to the node\n this._appendCSS(this._getCSS(copy), copy);\n let svg_markup = serializer.serializeToString(copy);\n resolve(svg_markup);\n });\n }\n\n /**\n * Converts the SVG string into a downloadable binary object\n * @return {Promise}\n */\n _getBlobUrl() {\n return this._generateSVG().then((markup) => {\n const blob = new Blob([markup], { type: 'image/svg+xml' });\n return URL.createObjectURL(blob);\n });\n }\n}\n\n/**\n * Button to export current plot to a PNG image\n * @alias module:LocusZoom_Widgets~download_png\n * @extends module:LocusZoom_Widgets~download_svg\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DownloadPNG extends DownloadSVG {\n /**\n * @param {string} [layout.button_html=\"Download PNG\"]\n * @param {string} [layout.button_title=\"Download image\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\n * @param {string} [layout.custom_event_name='widget_save_png'] The name of the event that will be emitted when the button is clicked\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\n constructor(layout, parent) {\n super(...arguments);\n this._filename = this.layout.filename || 'locuszoom.png';\n this._button_html = this.layout.button_html || 'Save PNG';\n this._button_title = this.layout.button_title || 'Download image';\n this._event_name = layout.custom_event_name || 'widget_save_png';\n }\n\n /**\n * @private\n */\n _getBlobUrl() {\n return super._getBlobUrl().then((svg_url) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n const [width, height] = this._getDimensions();\n\n canvas.width = width;\n canvas.height = height;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n // Once canvas rendered, revoke svg blob to avoid memory leaks, and create new url for the canvas\n URL.revokeObjectURL(svg_url);\n canvas.toBlob((png) => {\n resolve(URL.createObjectURL(png));\n });\n };\n image.src = svg_url;\n });\n });\n }\n}\n\n/**\n * Button to remove panel from plot.\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~remove_panel\n * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass RemovePanel extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('×')\n .setTitle('Remove panel')\n .setOnclick(() => {\n if (!this.layout.suppress_confirm && !confirm('Are you sure you want to remove this panel? This cannot be undone.')) {\n return false;\n }\n const panel = this.parent_panel;\n panel.toolbar.hide(true);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseover.${panel.getBaseId()}.toolbar`, null);\n d3.select(panel.parent.svg.node().parentNode).on(`mouseout.${panel.getBaseId()}.toolbar`, null);\n return panel.parent.removePanel(panel.id);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to move panel up relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_up\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelUp extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_top = (this.parent_panel.layout.y_index === 0);\n this.button.disable(is_at_top);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▴')\n .setTitle('Move panel up')\n .setOnclick(() => {\n this.parent_panel.moveUp();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to move panel down relative to other panels (in terms of y-index on the page)\n * NOTE: Will only work on panel widgets.\n * @alias module:LocusZoom_Widgets~move_panel_down\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass MovePanelDown extends BaseWidget {\n update () {\n if (this.button) {\n const is_at_bottom = (this.parent_panel.layout.y_index === this.parent_plot._panel_ids_by_y_index.length - 1);\n this.button.disable(is_at_bottom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml('▾')\n .setTitle('Move panel down')\n .setOnclick(() => {\n this.parent_panel.moveDown();\n this.update();\n });\n this.button.show();\n return this.update();\n }\n}\n\n/**\n * Button to shift plot region forwards or back by a `step` increment provided in the layout\n * @alias module:LocusZoom_Widgets~shift_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ShiftRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 50000;\n }\n if (typeof layout.button_html !== 'string') {\n layout.button_html = layout.step > 0 ? '>' : '<';\n }\n\n if (typeof layout.button_title !== 'string') {\n layout.button_title = `Shift region by ${layout.step > 0 ? '+' : '-'}${positionIntToString(Math.abs(layout.step), null, true)}`;\n }\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add shift_region toolbar widget: plot state does not have region bounds');\n }\n\n\n }\n\n update () {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start + this.layout.step, 1),\n end: this.parent_plot.state.end + this.layout.step,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Zoom in or out on the plot, centered on the middle of the plot region, by the specified amount\n * @alias module:LocusZoom_Widgets~zoom_region\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ZoomRegion extends BaseWidget {\n /**\n * @param {number} [layout.step=0.2] The fraction to zoom in by (where 1 indicates 100%)\n * @param {string} [layout.button_html] Label\n * @param {string} [layout.button_title] Mouseover text\n */\n constructor(layout, parent) {\n if (isNaN(layout.step) || layout.step === 0) {\n layout.step = 0.2;\n }\n if (typeof layout.button_html != 'string') {\n layout.button_html = layout.step > 0 ? 'z–' : 'z+';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = `Zoom region ${layout.step > 0 ? 'out' : 'in'} by ${(Math.abs(layout.step) * 100).toFixed(1)}%`;\n }\n\n super(layout, parent);\n if (isNaN(this.parent_plot.state.start) || isNaN(this.parent_plot.state.end)) {\n throw new Error('Unable to add zoom_region toolbar widget: plot state does not have region bounds');\n }\n }\n\n update () {\n if (this.button) {\n let can_zoom = true;\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n if (this.layout.step > 0 && !isNaN(this.parent_plot.layout.max_region_scale) && current_region_scale >= this.parent_plot.layout.max_region_scale) {\n can_zoom = false;\n }\n if (this.layout.step < 0 && !isNaN(this.parent_plot.layout.min_region_scale) && current_region_scale <= this.parent_plot.layout.min_region_scale) {\n can_zoom = false;\n }\n this.button.disable(!can_zoom);\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title)\n .setOnclick(() => {\n const current_region_scale = this.parent_plot.state.end - this.parent_plot.state.start;\n const zoom_factor = 1 + this.layout.step;\n let new_region_scale = current_region_scale * zoom_factor;\n if (!isNaN(this.parent_plot.layout.max_region_scale)) {\n new_region_scale = Math.min(new_region_scale, this.parent_plot.layout.max_region_scale);\n }\n if (!isNaN(this.parent_plot.layout.min_region_scale)) {\n new_region_scale = Math.max(new_region_scale, this.parent_plot.layout.min_region_scale);\n }\n const delta = Math.floor((new_region_scale - current_region_scale) / 2);\n this.parent_plot.applyState({\n start: Math.max(this.parent_plot.state.start - delta, 1),\n end: this.parent_plot.state.end + delta,\n });\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Renders button with arbitrary text that, when clicked, shows a dropdown containing arbitrary HTML. This is usually\n * used as part of coding a custom button, rather than as a standalone widget.\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @alias module:LocusZoom_Widgets~menu\n * @param {string} layout.button_html The HTML to render inside the button\n * @param {string} layout.button_title Text to display as a tooltip when hovering over the button\n * @param {string} layout.menu_html The HTML content of the dropdown menu\n */\nclass Menu extends BaseWidget {\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html)\n .setTitle(this.layout.button_title);\n this.button.menu.setPopulate(() => {\n this.button.menu.inner_selector.html(this.layout.menu_html);\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to resize panel height to fit available data (eg when showing a list of tracks)\n * @alias module:LocusZoom_Widgets~resize_to_data\n */\nclass ResizeToData extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\n constructor(layout) {\n super(...arguments);\n }\n update() {\n if (this.button) {\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setHtml(this.layout.button_html || 'Resize to Data')\n .setTitle(this.layout.button_title || 'Automatically resize this panel to show all data available')\n .setOnclick(() => {\n this.parent_panel.scaleHeightToData();\n this.update();\n });\n this.button.show();\n return this;\n }\n}\n\n/**\n * Button to toggle legend\n * @alias module:LocusZoom_Widgets~toggle_legend\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass ToggleLegend extends BaseWidget {\n update() {\n const html = this.parent_panel.legend.layout.hidden ? 'Show Legend' : 'Hide Legend';\n if (this.button) {\n this.button.setHtml(html).show();\n this.parent.position();\n return this;\n }\n this.button = new Button(this)\n .setColor(this.layout.color)\n .setTitle('Show or hide the legend for this panel')\n .setOnclick(() => {\n this.parent_panel.legend.layout.hidden = !this.parent_panel.legend.layout.hidden;\n this.parent_panel.legend.render();\n this.update();\n });\n return this.update();\n }\n}\n\n\n/**\n * @typedef {object} DisplayOptionsButtonConfigField\n * @property {string} display_name The human-readable label for this set of options\n * @property {object} display An object with layout directives that will be merged into the target layer.\n * The directives should be among those listed in `fields_whitelist` for this widget.\n */\n\n/**\n * The user has chosen a specific display option to show information on the plot\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_display_options_choice\n * @property {Object} data {choice} The display_name of the item chosen from the list\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to choose between different display options for a single specific data layer\n * within a panel.\n *\n * This allows controlling how points on a datalayer can be displayed- any display options supported via the layout for the target datalayer. This includes point\n * size/shape, coloring, etc.\n *\n * This button intentionally limits display options it can control to those available on common plot types.\n * Although the list of options it sets can be overridden (to control very special custom plot types), this\n * capability should be used sparingly if at all.\n * @alias module:LocusZoom_Widgets~display_options\n * @see {@link module:LocusZoom_Widgets~BaseWidget} for additional options\n */\nclass DisplayOptions extends BaseWidget {\n /**\n * @param {string} layout.layer_name Specify the datalayer that this button should affect\n * @param {String} [layout.button_html=\"Display options...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Control how plot items are displayed\"] Hover text for the toolbar button\n * @param {string} [layout.default_config_display_name] Store the default configuration for this datalayer\n * configuration, and show a button to revert to the \"default\" (listing the human-readable display name provided)\n * @param {Array} [layout.fields_whitelist='see code'] The list of presentation fields that this button can control.\n * This can be overridden if this button needs to be used on a custom layer type with special options.\n * The whitelist is chosen to be things that are known to be easily modified with few side effects.\n * When the button is first created, all fields in the whitelist will have their default values saved, so the user can revert to the default view easily.\n * @param {module:LocusZoom_Widgets~DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes that will be merged to datalayer layout options.\n * @param {string} [layout.custom_event_name='widget_display_options_choice'] The name of the event that will be emitted when an option is selected\n */\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Display options...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Control how plot items are displayed';\n }\n super(...arguments);\n this._event_name = layout.custom_event_name || 'widget_display_options_choice';\n\n // List of layout fields that this button is allowed to control. This ensures that we don't override any other\n // information (like plot height etc) while changing point rendering\n const allowed_fields = layout.fields_whitelist || ['color', 'fill_opacity', 'filters', 'label', 'legend',\n 'point_shape', 'point_size', 'tooltip', 'tooltip_positioning'];\n\n const dataLayer = this.parent_panel.data_layers[layout.layer_name];\n if (!dataLayer) {\n throw new Error(`Display options could not locate the specified layer_name: '${layout.layer_name}'`);\n }\n const dataLayerLayout = dataLayer.layout;\n\n // Store default configuration for the layer as a clean deep copy, so we may revert later\n const defaultConfig = {};\n allowed_fields.forEach((name) => {\n const configSlot = dataLayerLayout[name];\n if (configSlot !== undefined) {\n defaultConfig[name] = deepCopy(configSlot);\n }\n });\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n this._selected_item = 'default';\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html)\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const menuLayout = this.layout;\n\n const renderRow = (display_name, display_options, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `display-option-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (row_id === this._selected_item))\n .on('click', () => {\n // If an option is not specified in these display options, use the original defaults\n allowed_fields.forEach((field_name) => {\n const has_option = typeof display_options[field_name] !== 'undefined';\n dataLayer.layout[field_name] = has_option ? display_options[field_name] : defaultConfig[field_name];\n });\n\n this.parent_svg.emit(this._event_name, { choice: display_name }, true);\n this._selected_item = row_id;\n this.parent_panel.render();\n const legend = this.parent_panel.legend;\n if (legend) {\n legend.render();\n }\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n // Render the \"display options\" menu: default and special custom options\n const defaultName = menuLayout.default_config_display_name || 'Default style';\n renderRow(defaultName, defaultConfig, 'default');\n menuLayout.options.forEach((item, index) => renderRow(item.display_name, item.display, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n/**\n * @typedef {object} SetStateOptionsConfigField\n * @property {string} display_name Human readable name for option label (eg \"European\")\n * @property value Value to set in plot.state (eg \"EUR\")\n */\n\n/**\n * An option has been chosen from the set_state dropdown menu\n * Note: The widget can optionally be configured to broadcast this event under an alias (layout.custom_event_name)\n *\n * @event widget_set_state_choice\n * @property {Object} data { choice_name, choice_value, state_field }\n * @see event:any_lz_event\n */\n\n/**\n * Dropdown menu allowing the user to set the value of a specific `state_field` in plot.state\n * This is useful for things (like datasources) that allow dynamic configuration based on global information in state\n *\n * For example, the LDServer data adapter can use it to change LD reference population (for all panels) after render\n *\n * @alias module:LocusZoom_Widgets~set_state\n * @param {String} [layout.button_html=\"Set option...\"] Text to display on the toolbar button\n * @param {String} [layout.button_title=\"Choose an option to customize the plot\"] Hover text for the toolbar button\n * @param {bool} [layout.show_selected=false] Whether to append the selected value to the button label (\"LD Population: ALL\")\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @param {module:LocusZoom_Widgets~SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\n * @param {string} [layout.custom_event_name='widget_set_state_choice'] The name of the event that will be emitted when an option is selected\n */\nclass SetState extends BaseWidget {\n constructor(layout, parent) {\n if (typeof layout.button_html != 'string') {\n layout.button_html = 'Set option...';\n }\n if (typeof layout.button_title != 'string') {\n layout.button_title = 'Choose an option to customize the plot';\n }\n\n super(layout, parent);\n\n if (this.parent_panel) {\n throw new Error('This widget is designed to set global options, so it can only be used at the top (plot) level');\n }\n if (!layout.state_field) {\n throw new Error('Must specify the `state_field` that this widget controls');\n }\n\n this._event_name = layout.custom_event_name || 'widget_set_state_choice';\n\n /**\n * Which item in the menu is currently selected. (track for rerendering menu)\n * @member {String}\n * @private\n */\n // The first option listed is automatically assumed to be the default, unless a value exists in plot.state\n this._selected_item = this.parent_plot.state[layout.state_field] || layout.options[0].value;\n if (!layout.options.find((item) => {\n return item.value === this._selected_item;\n })) {\n // Check only gets run at widget creation, but generally this widget is assumed to be an exclusive list of options\n throw new Error('There is an existing state value that does not match the known values in this widget');\n }\n\n // Define the button + menu that provides the real functionality for this toolbar widget\n this.button = new Button(this)\n .setColor(layout.color)\n .setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''))\n .setTitle(layout.button_title)\n .setOnclick(() => {\n this.button.menu.populate();\n });\n this.button.menu.setPopulate(() => {\n // Multiple copies of this button might be used on a single LZ page; append unique IDs where needed\n const uniqueID = Math.floor(Math.random() * 1e4).toString();\n\n this.button.menu.inner_selector.html('');\n const table = this.button.menu.inner_selector.append('table');\n\n const renderRow = (display_name, value, row_id) => { // Helper method\n const row = table.append('tr');\n const radioId = `${uniqueID}${row_id}`;\n row.append('td')\n .append('input')\n .attr('id', radioId)\n .attr('type', 'radio')\n .attr('name', `set-state-${uniqueID}`)\n .attr('value', row_id)\n .style('margin', 0) // Override css libraries (eg skeleton) that style form inputs\n .property('checked', (value === this._selected_item))\n .on('click', () => {\n const new_state = {};\n new_state[layout.state_field] = value;\n this._selected_item = value;\n this.parent_plot.applyState(new_state);\n this.button.setHtml(layout.button_html + (layout.show_selected ? this._selected_item : ''));\n\n this.parent_svg.emit(this._event_name, { choice_name: display_name, choice_value: value, state_field: layout.state_field }, true);\n });\n row.append('td').append('label')\n .style('font-weight', 'normal')\n .attr('for', radioId)\n .text(display_name);\n };\n layout.options.forEach((item, index) => renderRow(item.display_name, item.value, index));\n return this;\n });\n }\n\n update() {\n this.button.show();\n return this;\n }\n}\n\n\nexport {\n BaseWidget, // This is used to create subclasses\n Button as _Button, // This is used to create Widgets that contain a button. It actually shouldn't be in the registry because it's not usable directly..\n DisplayOptions as display_options,\n DownloadSVG as download,\n DownloadPNG as download_png,\n FilterField as filter_field,\n Menu as menu,\n MovePanelDown as move_panel_down,\n MovePanelUp as move_panel_up,\n RegionScale as region_scale,\n ResizeToData as resize_to_data,\n SetState as set_state,\n ShiftRegion as shift_region,\n RemovePanel as remove_panel,\n Title as title,\n ToggleLegend as toggle_legend,\n ZoomRegion as zoom_region,\n};\n","import {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided toolbar widgets: interactive buttons\n * and menus that control plot display, modify data, or show additional information as context.\n * @alias module:LocusZoom~Widgets\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\n\nfor (let [name, type] of Object.entries(widgets)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","import WIDGETS from '../../registry/widgets';\nimport * as d3 from 'd3';\n\n/**\n * A Toolbar is an HTML element used for presenting arbitrary user interface widgets. Toolbars are anchored\n * to either the entire Plot or to individual Panels.\n *\n * Each toolbar is an HTML-based (read: not SVG) collection of widgets used to display information or provide\n * user interface. Toolbars can exist on entire plots, where their visibility is permanent and vertically adjacent\n * to the plot, or on individual panels, where their visibility is tied to a behavior (e.g. a mouseover) and is as\n * an overlay.\n *\n * This class is used internally for rendering, and is not part of the public interface\n * @private\n */\nclass Toolbar {\n constructor(parent) {\n // parent must be a locuszoom plot or panel\n // if (!(parent instanceof LocusZoom.Plot) && !(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create toolbar, parent must be a locuszoom plot or panel');\n // }\n /** @member {Plot|Panel} */\n this.parent = parent;\n\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.toolbar`;\n\n /** @member {('plot'|'panel')} */\n this.type = (this.parent.parent) ? 'panel' : 'plot';\n\n /** @member {Plot} */\n this.parent_plot = this.parent.parent_plot;\n\n /** @member {d3.selection} */\n this.selector = null;\n\n /** @member {BaseWidget[]} */\n this.widgets = [];\n\n /**\n * The timer identifier as returned by setTimeout\n * @member {Number}\n */\n this.hide_timeout = null;\n\n /**\n * Whether to hide the toolbar. Can be overridden by a child widget. Check via `shouldPersist`\n * @protected\n * @member {Boolean}\n */\n this.persist = false;\n\n this.initialize();\n }\n\n /**\n * Prepare the toolbar for first use: generate all widget instances for this toolbar, based on the provided\n * layout of the parent. Connects event listeners and shows/hides as appropriate.\n * @returns {Toolbar}\n */\n initialize() {\n // Parse layout to generate widget instances\n const options = this.parent.layout.toolbar.widgets;\n if (Array.isArray(options)) {\n options.forEach((layout) => {\n this.addWidget(layout);\n });\n }\n\n // Add mouseover event handlers to show/hide panel toolbar (plot toolbar will always be shown)\n if (this.type === 'panel') {\n d3.select(this.parent.parent.svg.node().parentNode)\n .on(`mouseover.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n if (!this.selector || this.selector.style('visibility') === 'hidden') {\n this.show();\n }\n }).on(`mouseout.${this.id}`, () => {\n clearTimeout(this.hide_timeout);\n this.hide_timeout = setTimeout(() => {\n this.hide();\n }, 300);\n });\n }\n\n return this;\n }\n\n /**\n * Add a new widget to the toolbar.\n * FIXME: Kludgy to use. In the very rare cases where a widget is added dynamically, the caller will need to:\n * - add the widget to plot.layout.toolbar.widgets, AND calling it with the same object reference here.\n * - call widget.show() to ensure that the widget is initialized and rendered correctly\n * When creating an existing plot defined in advance, neither of these actions is needed and so we don't do this by default.\n * @param {Object} layout The layout object describing the desired widget\n * @returns {layout.type}\n */\n addWidget(layout) {\n try {\n const widget = WIDGETS.create(layout.type, layout, this);\n this.widgets.push(widget);\n return widget;\n } catch (e) {\n console.warn('Failed to create widget');\n console.error(e);\n }\n }\n\n /**\n * Whether to persist the toolbar. Returns true if at least one widget should persist, or if the panel is engaged\n * in an active drag event.\n * @returns {boolean}\n */\n shouldPersist() {\n if (this.persist) {\n return true;\n }\n let persist = false;\n // Persist if at least one widget should also persist\n this.widgets.forEach((widget) => {\n persist = persist || widget.shouldPersist();\n });\n // Persist if in a parent drag event\n persist = persist || (this.parent_plot._panel_boundaries.dragging || this.parent_plot._interaction.dragging);\n return !!persist;\n }\n\n /**\n * Make the toolbar appear. If it doesn't exist yet create it, including creating/positioning all widgets within,\n * and make sure it is set to be visible.\n */\n show() {\n if (!this.selector) {\n switch (this.type) {\n case 'plot':\n this.selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', ':first-child');\n break;\n case 'panel':\n this.selector = d3.select(this.parent.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip, .lz-toolbar-menu, .lz-curtain').classed('lz-panel-toolbar', true);\n break;\n default:\n throw new Error(`Toolbar cannot be a child of ${this.type}`);\n }\n\n this.selector\n .classed('lz-toolbar', true)\n .classed(`lz-${this.type}-toolbar`, true)\n .attr('id', this.id);\n }\n this.widgets.forEach((widget) => widget.show());\n this.selector.style('visibility', 'visible');\n return this.update();\n }\n\n\n /**\n * Update the toolbar and rerender all child widgets. This can be called whenever plot state changes.\n * @returns {Toolbar}\n */\n update() {\n if (!this.selector) {\n return this;\n }\n this.widgets.forEach((widget) => widget.update());\n return this.position();\n }\n\n\n /**\n * Position the toolbar (and child widgets) within the panel\n * @returns {Toolbar}\n */\n position() {\n if (!this.selector) {\n return this;\n }\n // Position the toolbar itself (panel only)\n if (this.type === 'panel') {\n const page_origin = this.parent._getPageOrigin();\n const top = `${(page_origin.y + 3.5).toString()}px`;\n const left = `${page_origin.x.toString()}px`;\n const width = `${(this.parent_plot.layout.width - 4).toString()}px`;\n this.selector\n .style('position', 'absolute')\n .style('top', top)\n .style('left', left)\n .style('width', width);\n }\n // Recursively position widgets\n this.widgets.forEach((widget) => widget.position());\n return this;\n }\n\n /**\n * Hide the toolbar (make invisible but do not destroy). Will do nothing if `shouldPersist` returns true.\n *\n * @returns {Toolbar}\n */\n hide() {\n if (!this.selector || this.shouldPersist()) {\n return this;\n }\n this.widgets.forEach((widget) => widget.hide());\n this.selector\n .style('visibility', 'hidden');\n return this;\n }\n\n /**\n * Completely remove toolbar and all child widgets. (may be overridden by persistence settings)\n * @param {Boolean} [force=false] If true, will ignore persistence settings and always destroy the toolbar\n * @returns {Toolbar}\n */\n destroy(force) {\n if (typeof force == 'undefined') {\n force = false;\n }\n if (!this.selector) {\n return this;\n }\n if (this.shouldPersist() && !force) {\n return this;\n }\n this.widgets.forEach((widget) => widget.destroy(true));\n this.widgets = [];\n this.selector.remove();\n this.selector = null;\n return this;\n }\n}\n\n\nexport {Toolbar as default};\n","/**\n * @module\n * @private\n */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\n// FIXME: Document legend options\n/**\n * The default layout used by legends (used internally)\n * @protected\n * @member {Object}\n */\nconst default_layout = {\n orientation: 'vertical',\n origin: { x: 0, y: 0 },\n width: 10,\n height: 10,\n padding: 5,\n label_size: 14,\n hidden: false,\n};\n\n/**\n * An SVG object used to display contextual information about a panel.\n * Panel layouts determine basic features of a legend - its position in the panel, orientation, title, etc.\n * Layouts of child data layers of the panel determine the actual content of the legend.\n *\n * @param {Panel} parent\n*/\nclass Legend {\n constructor(parent) {\n // if (!(parent instanceof LocusZoom.Panel)) {\n // throw new Error('Unable to create legend, parent must be a locuszoom panel');\n // }\n /** @member {Panel} */\n this.parent = parent;\n /** @member {String} */\n this.id = `${this.parent.getBaseId()}.legend`;\n\n this.parent.layout.legend = merge(this.parent.layout.legend || {}, default_layout);\n /** @member {Object} */\n this.layout = this.parent.layout.legend;\n\n /** @member {d3.selection} */\n this.selector = null;\n /** @member {d3.selection} */\n this.background_rect = null;\n /** @member {d3.selection[]} */\n this.elements = [];\n /**\n * SVG selector for the group containing all elements in the legend\n * @protected\n * @member {d3.selection|null}\n */\n this.elements_group = null;\n\n /**\n * TODO: Not sure if this property is used; the external-facing methods are setting `layout.hidden` instead. Tentatively mark deprecated.\n * @deprecated\n * @protected\n * @member {Boolean}\n */\n this.hidden = false;\n\n return this.render();\n }\n\n /**\n * Render the legend in the parent panel\n */\n render() {\n // Get a legend group selector if not yet defined\n if (!this.selector) {\n this.selector = this.parent.svg.group.append('g')\n .attr('id', `${this.parent.getBaseId()}.legend`).attr('class', 'lz-legend');\n }\n\n // Get a legend background rect selector if not yet defined\n if (!this.background_rect) {\n this.background_rect = this.selector.append('rect')\n .attr('width', 100)\n .attr('height', 100)\n .attr('class', 'lz-legend-background');\n }\n\n // Get a legend elements group selector if not yet defined\n if (!this.elements_group) {\n this.elements_group = this.selector.append('g');\n }\n\n // Remove all elements from the document and re-render from scratch\n this.elements.forEach((element) => element.remove());\n this.elements = [];\n\n // Gather all elements from data layers in order (top to bottom) and render them\n const padding = +this.layout.padding || 1;\n let x = padding;\n let y = padding;\n let line_height = 0;\n this.parent._data_layer_ids_by_z_index.slice().reverse().forEach((id) => {\n const layer_legend = this.parent.data_layers[id].layout.legend;\n if (Array.isArray(layer_legend)) {\n layer_legend.forEach((element) => {\n const selector = this.elements_group.append('g')\n .attr('transform', `translate(${x}, ${y})`);\n const label_size = +element.label_size || +this.layout.label_size;\n let label_x = 0;\n let label_y = (label_size / 2) + (padding / 2);\n line_height = Math.max(line_height, label_size + padding);\n // Draw the legend element symbol (line, rect, shape, etc)\n const shape = element.shape || '';\n const shape_factory = nameToSymbol(shape);\n if (shape === 'line') {\n // Line symbol\n const length = +element.length || 16;\n const path_y = (label_size / 4) + (padding / 2);\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', `M0,${path_y}L${length},${path_y}`)\n .call(applyStyles, element.style || {});\n label_x = length + padding;\n } else if (shape === 'rect') {\n // Rect symbol\n const width = +element.width || 16;\n const height = +element.height || width;\n selector\n .append('rect')\n .attr('class', element.class || '')\n .attr('width', width)\n .attr('height', height)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = width + padding;\n line_height = Math.max(line_height, height + padding);\n } else if (shape === 'ribbon') {\n // Color ribbons describe a series of color stops: small boxes of color across a continuous\n // scale. Drawn horizontally, or vertically, like:\n // [red | orange | yellow | green ] label\n // For example, this can be used with the numerical-bin color scale to describe LD color stops in a compact way.\n const width = +element.width || 25;\n const height = +element.height || width;\n const is_horizontal = (element.orientation || 'vertical') === 'horizontal';\n let color_stops = element.color_stops;\n\n const all_elements = selector.append('g');\n const ribbon_group = all_elements.append('g');\n const axis_group = all_elements.append('g');\n let axis_offset = 0;\n if (element.tick_labels) {\n let range;\n if (is_horizontal) {\n range = [0, width * color_stops.length - 1]; // 1 px offset to align tick with inner borders\n } else {\n range = [height * color_stops.length - 1, 0];\n }\n const scale = d3.scaleLinear()\n .domain(d3.extent(element.tick_labels)) // Assumes tick labels are always numeric in this mode\n .range(range);\n const axis = (is_horizontal ? d3.axisTop : d3.axisRight)(scale)\n .tickSize(3)\n .tickValues(element.tick_labels)\n .tickFormat((v) => v);\n axis_group\n .call(axis)\n .attr('class', 'lz-axis');\n let bcr = axis_group.node().getBoundingClientRect();\n axis_offset = bcr.height;\n }\n if (is_horizontal) {\n // Shift axis down (so that tick marks aren't above the origin)\n axis_group\n .attr('transform', `translate(0, ${axis_offset})`);\n // Ribbon appears below axis\n ribbon_group\n .attr('transform', `translate(0, ${axis_offset})`);\n } else {\n // Vertical mode: Shift axis ticks to the right of the ribbon\n all_elements.attr('transform', 'translate(5, 0)');\n axis_group\n .attr('transform', `translate(${width}, 0)`);\n }\n\n if (!is_horizontal) {\n // Vertical mode: renders top -> bottom but scale is usually specified low..high\n color_stops = color_stops.slice();\n color_stops.reverse();\n }\n for (let i = 0; i < color_stops.length; i++) {\n const color = color_stops[i];\n const to_next_marking = is_horizontal ? `translate(${width * i}, 0)` : `translate(0, ${height * i})`;\n ribbon_group\n .append('rect')\n .attr('class', element.class || '')\n .attr('stroke', 'black')\n .attr('transform', to_next_marking)\n .attr('stroke-width', 0.5)\n .attr('width', width)\n .attr('height', height)\n .attr('fill', color)\n .call(applyStyles, element.style || {});\n }\n\n // Note: In vertical mode, it's usually easier to put the label above the legend as a separate marker\n // This is because the legend element label is drawn last (can't use it's size to position the ribbon, which is drawn first)\n if (!is_horizontal && element.label) {\n throw new Error('Legend labels not supported for vertical ribbons (use a separate legend item as text instead)');\n }\n // This only makes sense for horizontal labels.\n label_x = (width * color_stops.length + padding);\n label_y += axis_offset;\n } else if (shape_factory) {\n // Shape symbol is a recognized d3 type, so we can draw it in the legend (circle, diamond, etc.)\n const size = +element.size || 40;\n const radius = Math.ceil(Math.sqrt(size / Math.PI));\n selector\n .append('path')\n .attr('class', element.class || '')\n .attr('d', d3.symbol().size(size).type(shape_factory))\n .attr('transform', `translate(${radius}, ${radius + (padding / 2)})`)\n .attr('fill', element.color || {})\n .call(applyStyles, element.style || {});\n\n label_x = (2 * radius) + padding;\n label_y = Math.max((2 * radius) + (padding / 2), label_y);\n line_height = Math.max(line_height, (2 * radius) + padding);\n }\n // Draw the legend element label\n selector\n .append('text')\n .attr('text-anchor', 'left')\n .attr('class', 'lz-label')\n .attr('x', label_x)\n .attr('y', label_y)\n .style('font-size', label_size)\n .text(element.label);\n\n // Position the legend element group based on legend layout orientation\n const bcr = selector.node().getBoundingClientRect();\n if (this.layout.orientation === 'vertical') {\n y += bcr.height + padding;\n line_height = 0;\n } else {\n // Ensure this element does not exceed the panel width\n // (E.g. drop to the next line if it does, but only if it's not the only element on this line)\n const right_x = this.layout.origin.x + x + bcr.width;\n if (x > padding && right_x > this.parent.parent.layout.width) {\n y += line_height;\n x = padding;\n selector.attr('transform', `translate(${x}, ${y})`);\n }\n x += bcr.width + (3 * padding);\n }\n // Store the element\n this.elements.push(selector);\n });\n }\n });\n\n // Scale the background rect to the elements in the legend\n const bcr = this.elements_group.node().getBoundingClientRect();\n this.layout.width = bcr.width + (2 * this.layout.padding);\n this.layout.height = bcr.height + (2 * this.layout.padding);\n this.background_rect\n .attr('width', this.layout.width)\n .attr('height', this.layout.height);\n\n // Set the visibility on the legend from the \"hidden\" flag\n // TODO: `show()` and `hide()` call a full rerender; might be able to make this more lightweight?\n this.selector\n .style('visibility', this.layout.hidden ? 'hidden' : 'visible');\n\n return this.position();\n }\n\n /**\n * Place the legend in position relative to the panel, as specified in the layout configuration\n * @returns {Legend | null}\n * TODO: should this always be chainable?\n */\n position() {\n if (!this.selector) {\n return this;\n }\n const bcr = this.selector.node().getBoundingClientRect();\n if (!isNaN(+this.layout.pad_from_bottom)) {\n this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom;\n }\n if (!isNaN(+this.layout.pad_from_right)) {\n this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right;\n }\n this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n }\n\n /**\n * Hide the legend (triggers a re-render)\n * @public\n */\n hide() {\n this.layout.hidden = true;\n this.render();\n }\n\n /**\n * Show the legend (triggers a re-render)\n * @public\n */\n show() {\n this.layout.hidden = false;\n this.render();\n }\n}\n\nexport {Legend as default};\n","import * as d3 from 'd3';\n\nimport {STATUSES} from './constants';\nimport Toolbar from './toolbar';\nimport {applyStyles, generateCurtain, generateLoader} from '../helpers/common';\nimport {parseFields, positionIntToString, prettyTicks} from '../helpers/display';\nimport {merge} from '../helpers/layouts';\nimport Legend from './legend';\nimport data_layers from '../registry/data_layers';\n\n\n/**\n * Default panel layout\n * @memberof Panel\n * @static\n * @type {Object}\n */\nconst default_layout = {\n id: '',\n tag: 'custom_data_type',\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1,\n height: 1,\n origin: { x: 0, y: null },\n margin: { top: 0, right: 0, bottom: 0, left: 0 },\n background_click: 'clear_selections',\n toolbar: {\n widgets: [],\n },\n cliparea: {\n height: 0,\n width: 0,\n origin: { x: 0, y: 0 },\n },\n axes: { // These are the only axes supported!!\n x: {},\n y1: {},\n y2: {},\n },\n legend: null,\n interaction: {\n drag_background_to_pan: false,\n drag_x_ticks_to_scale: false,\n drag_y1_ticks_to_scale: false,\n drag_y2_ticks_to_scale: false,\n scroll_to_zoom: false,\n x_linked: false,\n y1_linked: false,\n y2_linked: false,\n },\n show_loading_indicator: true,\n data_layers: [],\n};\n\n/**\n * A panel is an abstract class representing a subdivision of the LocusZoom stage\n * to display a distinct data representation as a collection of data layers.\n */\nclass Panel {\n /**\n * @param {string} layout.id An identifier string that must be unique across all panels in the plot. Required.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every panel\n * that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in panels will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {boolean} [layout.show_loading_indicator=true] Whether to show a \"loading indicator\" while data is being fetched\n * @param {module:LocusZoom_DataLayers[]} [layout.data_layers] Data layer layout objects\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each toolbar widget; {@link module:LocusZoom_Widgets}\n * @param {number} [layout.title.text] Text to show in panel title\n * @param {number} [layout.title.style] CSS options to apply to the title\n * @param {number} [layout.title.x=10] x-offset for title position\n * @param {number} [layout.title.y=22] y-offset for title position\n * @param {'vertical'|'horizontal'} [layout.legend.orientation='vertical'] Orientation with which elements in the legend should be arranged.\n * Presently only \"vertical\" and \"horizontal\" are supported values. When using the horizontal orientation\n * elements will automatically drop to a new line if the width of the legend would exceed the right edge of the\n * containing panel. Defaults to \"vertical\".\n * @param {number} [layout.legend.origin.x=0] X-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * @param {number} [layout.legend.origin.y=0] Y-offset, in pixels, for the top-left corner of the legend (relative to the top left corner of the panel).\n * NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {number} [layout.legend.padding=5] Value in pixels to pad between the legend's outer border and the\n * elements within the legend. This value is also used for spacing between elements in the legend on different\n * lines (e.g. in a vertical orientation) and spacing between element shapes and labels, as well as between\n * elements in a horizontal orientation, are defined as a function of this value. Defaults to 5.\n * @param {number} [layout.legend.label_size=12] Font size for element labels in the legend (loosely analogous to the height of full-height letters, in pixels). Defaults to 12.\n * @param {boolean} [layout.legend.hidden=false] Whether to hide the legend by default\n * @param {number} [layout.y_index] The position of the panel (above or below other panels). This is usually set\n * automatically when the panel is added, and rarely controlled directly.\n * @param {number} [layout.min_height=1] When resizing, do not allow height to go below this value\n * @param {number} [layout.height=1] The actual height allocated to the panel (>= min_height)\n * @param {number} [layout.margin.top=0] The margin (space between top of panel and edge of viewing area)\n * @param {number} [layout.margin.right=0] The margin (space between right side of panel and edge of viewing area)\n * @param {number} [layout.margin.bottom=0] The margin (space between bottom of panel and edge of viewing area)\n * @param {number} [layout.margin.left=0] The margin (space between left side of panel and edge of viewing area)\n * @param {'clear_selections'|null} [layout.background_click='clear_selections'] What happens when the background of the panel is clicked\n * @param {'state'|null} [layout.axes.x.extent] If 'state', the x extent will be determined from plot.state (a\n * shared region). Otherwise it will be determined based on data later ranges.\n * @param {string} [layout.axes.x.label] Label text for the provided axis\n * @param {number} [layout.axes.x.label_offset]\n * @param {boolean} [layout.axes.x.render] Whether to render this axis\n * @param {'region'|null} [layout.axes.x.tick_format] If 'region', format ticks in a concise way suitable for\n * genomic coordinates, eg 23423456 => 23.42 (Mb)\n * @param {Array} [layout.axes.x.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y1.label] Label text for the provided axis\n * @param {number} [layout.axes.y1.label_offset] The distance between the axis title and the axis. Use this to prevent\n * the title from overlapping with tick mark labels. If there is not enough space for the label, be sure to increase the panel margins (left or right) accordingly.\n * @param {boolean} [layout.axes.y1.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y1.ticks] An array of custom ticks that will override any automatically generated)\n * @param {string} [layout.axes.y2.label] Label text for the provided axis\n * @param {number} [layout.axes.y2.label_offset]\n * @param {boolean} [layout.axes.y2.render=false] Whether to render this axis\n * @param {Array} [layout.axes.y2.ticks] An array of custom ticks that will override any automatically generated)\n * @param {boolean} [layout.interaction.drag_background_to_pan=false] Allow the user to drag the panel background to pan\n * the plot to another genomic region.\n * @param {boolean} [layout.interaction.drag_x_ticks_to_scale=false] Allow the user to rescale the x axis by dragging x ticks\n * @param {boolean} [layout.interaction.drag_y1_ticks_to_scale=false] Allow the user to rescale the y1 axis by dragging y1 ticks\n * @param {boolean} [layout.interaction.drag_y2_ticks_to_scale=false] Allow the user to rescale the y2 axis by dragging y2 ticks\n * @param {boolean} [layout.interaction.scroll_to_zoom=false] Allow the user to rescale the plot by mousewheel-scrolling\n * @param {boolean} [layout.interaction.x_linked=false] Whether this panel should change regions to match all other linked panels\n * @param {boolean} [layout.interaction.y1_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {boolean} [layout.interaction.y2_linked=false] Whether this panel should rescale to match all other linked panels\n * @param {Plot|null} parent\n */\n constructor(layout, parent) {\n if (typeof layout !== 'object') {\n throw new Error('Unable to create panel, invalid layout');\n }\n\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent = parent || null;\n /**\n * @protected\n * @member {Plot|null}\n */\n this.parent_plot = parent;\n\n if (typeof layout.id !== 'string' || !layout.id) {\n throw new Error('Panel layouts must specify \"id\"');\n } else if (this.parent) {\n if (typeof this.parent.panels[layout.id] !== 'undefined') {\n throw new Error(`Cannot create panel with id [${layout.id}]; panel with that id already exists`);\n }\n }\n /**\n * @public\n * @member {String}\n */\n this.id = layout.id;\n\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * The index of this panel in the parent plot's `layout.panels`\n * @private\n * @member {number}\n * */\n this._layout_idx = null;\n /**\n * @private\n * @member {Object}\n */\n this.svg = {};\n\n /**\n * A JSON-serializable object used to describe the composition of the Panel\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n\n // Define state parameters specific to this panel\n if (this.parent) {\n /**\n * @private\n * @member {Object}\n */\n this.state = this.parent.state;\n\n /**\n * @private\n * @member {String}\n */\n this._state_id = this.id;\n this.state[this._state_id] = this.state[this._state_id] || {};\n } else {\n this.state = null;\n this._state_id = null;\n }\n\n /**\n * Direct access to data layer instances, keyed by data layer ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.data_layers = {};\n /**\n * @private\n * @member {String[]}\n */\n this._data_layer_ids_by_z_index = [];\n\n /**\n * Track data requests in progress\n * @member {Promise[]}\n * @private\n */\n this._data_promises = [];\n\n /**\n * @private\n * @member {d3.scale}\n */\n this.x_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y1_scale = null;\n /**\n * @private\n * @member {d3.scale}\n */\n this.y2_scale = null;\n\n /**\n * @private\n * @member {d3.extent}\n */\n this.x_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y1_extent = null;\n /**\n * @private\n * @member {d3.extent}\n */\n this.y2_extent = null;\n\n /**\n * @private\n * @member {Number[]}\n */\n this.x_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y1_ticks = [];\n /**\n * @private\n * @member {Number[]}\n */\n this.y2_ticks = [];\n\n /**\n * A timeout ID as returned by setTimeout\n * @private\n * @member {number}\n */\n this._zoom_timeout = null;\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* Public methods: intended for direct external manipulation of panel internals */\n\n /**\n * There are several events that a LocusZoom panel can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `panel.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of the event. Consult documentation for the names of built-in events.\n * @param {function} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n // TODO: Dry plot and panel event code into a shared mixin\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Panel}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n *\n * There is a shorter overloaded form of this method: if the event does not have any data, the second\n * argument can be a boolean to control bubbling\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} [eventData] Data or event description that will be passed to the event listener\n * @param {boolean} [bubble=false] Whether to bubble the event to the parent\n * @returns {Panel}\n */\n emit(event, eventData, bubble) {\n bubble = bubble || false;\n\n // TODO: DRY this with the parent plot implementation. Ensure interfaces remain compatible.\n // TODO: Improve documentation for overloaded method signature (JSDoc may have trouble here)\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n if (typeof eventData === 'boolean' && arguments.length === 2) {\n // Overloaded method signature: emit(event, bubble)\n bubble = eventData;\n eventData = null;\n }\n const sourceID = this.getBaseId();\n const eventContext = { sourceID: sourceID, target: this, data: eventData || null };\n\n if (this._event_hooks[event]) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n this._event_hooks[event].forEach((hookToRun) => {\n // By default, any handlers fired here will see the panel as the value of `this`. If a bound function is\n // registered as a handler, the previously bound `this` will override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n if (bubble && this.parent) {\n // Even if this event has no listeners locally, it might still have listeners on the parent\n this.parent.emit(event, eventContext);\n }\n return this;\n }\n\n /**\n * Set the title for the panel. If passed an object, will merge the object with the existing layout configuration, so\n * that all or only some of the title layout object's parameters can be customized. If passed null, false, or an empty\n * string, the title DOM element will be set to display: none.\n *\n * @public\n * @param {string|object|null} title The title text, or an object with additional configuration\n * @param {string} title.text Text to display. Since titles are rendered as SVG text, HTML and newlines will not be rendered.\n * @param {number} title.x X-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n * @param {number} title.y Y-offset, in pixels, for the title's text anchor (default left) relative to the top-left corner of the panel.\n NOTE: SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner.\n * @param {object} title.style CSS styles object to be applied to the title's DOM element.\n * @returns {Panel}\n */\n setTitle(title) {\n if (typeof this.layout.title == 'string') {\n const text = this.layout.title;\n this.layout.title = { text: text, x: 0, y: 0, style: {} };\n }\n if (typeof title == 'string') {\n this.layout.title.text = title;\n } else if (typeof title == 'object' && title !== null) {\n this.layout.title = merge(title, this.layout.title);\n }\n if (this.layout.title.text.length) {\n this.title\n .attr('display', null)\n .attr('x', parseFloat(this.layout.title.x))\n .attr('y', parseFloat(this.layout.title.y))\n .text(this.layout.title.text)\n .call(applyStyles, this.layout.title.style);\n\n } else {\n this.title.attr('display', 'none');\n }\n return this;\n }\n\n /**\n * Create a new data layer from a provided layout object. Should have the keys specified in `DefaultLayout`\n * Will automatically add at the top (depth/z-index) of the panel unless explicitly directed differently\n * in the layout provided.\n *\n * **NOTE**: It is very rare that new data layers are added after a panel is rendered.\n * @public\n * @param {object} layout\n * @returns {BaseDataLayer}\n */\n addDataLayer(layout) {\n // Sanity checks\n if (typeof layout !== 'object' || typeof layout.id !== 'string' || !layout.id.length) {\n throw new Error('Invalid data layer layout');\n }\n if (typeof this.data_layers[layout.id] !== 'undefined') {\n throw new Error(`Cannot create data_layer with id '${layout.id}'; data layer with that id already exists in the panel`);\n }\n if (typeof layout.type !== 'string') {\n throw new Error('Invalid data layer type');\n }\n\n // If the layout defines a y axis make sure the axis number is set and is 1 or 2 (default to 1)\n if (typeof layout.y_axis == 'object' && (typeof layout.y_axis.axis == 'undefined' || ![1, 2].includes(layout.y_axis.axis))) {\n layout.y_axis.axis = 1;\n }\n\n // Create the Data Layer\n const data_layer = data_layers.create(layout.type, layout, this);\n\n // Store the Data Layer on the Panel\n this.data_layers[data_layer.id] = data_layer;\n\n // If a discrete z_index was set in the layout then adjust other data layer z_index values to accommodate this one\n if (data_layer.layout.z_index !== null && !isNaN(data_layer.layout.z_index)\n && this._data_layer_ids_by_z_index.length > 0) {\n // Negative z_index values should count backwards from the end, so convert negatives to appropriate values here\n if (data_layer.layout.z_index < 0) {\n data_layer.layout.z_index = Math.max(this._data_layer_ids_by_z_index.length + data_layer.layout.z_index, 0);\n }\n this._data_layer_ids_by_z_index.splice(data_layer.layout.z_index, 0, data_layer.id);\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n } else {\n const length = this._data_layer_ids_by_z_index.push(data_layer.id);\n this.data_layers[data_layer.id].layout.z_index = length - 1;\n }\n\n // Determine if this data layer was already in the layout.data_layers array.\n // If it wasn't, add it. Either way store the layout.data_layers array index on the data_layer.\n let layout_idx = null;\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n if (data_layer_layout.id === data_layer.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.data_layers.push(this.data_layers[data_layer.id].layout) - 1;\n }\n this.data_layers[data_layer.id]._layout_idx = layout_idx;\n\n return this.data_layers[data_layer.id];\n }\n\n /**\n * Remove a data layer by id\n * @public\n * @param {string} id\n * @returns {Panel}\n */\n removeDataLayer(id) {\n const target_layer = this.data_layers[id];\n if (!target_layer) {\n throw new Error(`Unable to remove data layer, ID not found: ${id}`);\n }\n\n // Destroy all tooltips for the data layer\n target_layer.destroyAllTooltips();\n\n // Remove the svg container for the data layer if it exists\n if (target_layer.svg.container) {\n target_layer.svg.container.remove();\n }\n\n // Delete the data layer and its presence in the panel layout and state\n this.layout.data_layers.splice(target_layer._layout_idx, 1);\n delete this.state[target_layer._state_id];\n delete this.data_layers[id];\n\n // Remove the data_layer id from the z_index array\n this._data_layer_ids_by_z_index.splice(this._data_layer_ids_by_z_index.indexOf(id), 1);\n\n // Update layout_idx and layout.z_index values for all remaining data_layers\n this.applyDataLayerZIndexesToDataLayerLayouts();\n this.layout.data_layers.forEach((data_layer_layout, idx) => {\n this.data_layers[data_layer_layout.id]._layout_idx = idx;\n });\n\n return this;\n }\n\n /**\n * Clear all selections on all data layers\n * @public\n * @returns {Panel}\n */\n clearSelections() {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus('selected', false);\n });\n return this;\n }\n\n /**\n * Update rendering of this panel whenever an event triggers a redraw. Assumes that the panel has already been\n * prepared the first time via `initialize`\n * @public\n * @returns {Panel}\n */\n render() {\n // Position the panel container\n this.svg.container.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`);\n\n // Set size on the clip rect\n this.svg.clipRect\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n const { cliparea } = this.layout;\n\n // Set and position the inner border, style if necessary\n const { margin } = this.layout;\n this.inner_border\n .attr('x', margin.left)\n .attr('y', margin.top)\n .attr('width', this.parent_plot.layout.width - (margin.left + margin.right))\n .attr('height', this.layout.height - (margin.top + margin.bottom));\n if (this.layout.inner_border) {\n this.inner_border\n .style('stroke-width', 1)\n .style('stroke', this.layout.inner_border);\n }\n\n // Set/update panel title if necessary\n this.setTitle();\n\n // Regenerate all extents\n this.generateExtents();\n\n // Helper function to constrain any procedurally generated vectors (e.g. ranges, extents)\n // Constraints applied here keep vectors from going to infinity or beyond a definable power of ten\n const constrain = function (value, limit_exponent) {\n const neg_min = Math.pow(-10, limit_exponent);\n const neg_max = Math.pow(-10, -limit_exponent);\n const pos_min = Math.pow(10, -limit_exponent);\n const pos_max = Math.pow(10, limit_exponent);\n if (value === Infinity) {\n value = pos_max;\n }\n if (value === -Infinity) {\n value = neg_min;\n }\n if (value === 0) {\n value = pos_min;\n }\n if (value > 0) {\n value = Math.max(Math.min(value, pos_max), pos_min);\n }\n if (value < 0) {\n value = Math.max(Math.min(value, neg_max), neg_min);\n }\n return value;\n };\n\n // Define default and shifted ranges for all axes\n const ranges = {};\n const axes_config = this.layout.axes;\n if (this.x_extent) {\n const base_x_range = { start: 0, end: this.layout.cliparea.width };\n if (axes_config.x.range) {\n base_x_range.start = axes_config.x.range.start || base_x_range.start;\n base_x_range.end = axes_config.x.range.end || base_x_range.end;\n }\n ranges.x = [base_x_range.start, base_x_range.end];\n ranges.x_shifted = [base_x_range.start, base_x_range.end];\n }\n if (this.y1_extent) {\n const base_y1_range = { start: cliparea.height, end: 0 };\n if (axes_config.y1.range) {\n base_y1_range.start = axes_config.y1.range.start || base_y1_range.start;\n base_y1_range.end = axes_config.y1.range.end || base_y1_range.end;\n }\n ranges.y1 = [base_y1_range.start, base_y1_range.end];\n ranges.y1_shifted = [base_y1_range.start, base_y1_range.end];\n }\n if (this.y2_extent) {\n const base_y2_range = { start: cliparea.height, end: 0 };\n if (axes_config.y2.range) {\n base_y2_range.start = axes_config.y2.range.start || base_y2_range.start;\n base_y2_range.end = axes_config.y2.range.end || base_y2_range.end;\n }\n ranges.y2 = [base_y2_range.start, base_y2_range.end];\n ranges.y2_shifted = [base_y2_range.start, base_y2_range.end];\n }\n\n // Shift ranges based on any drag or zoom interactions currently underway\n let { _interaction } = this.parent;\n const current_drag = _interaction.dragging;\n if (_interaction.panel_id && (_interaction.panel_id === this.id || _interaction.linked_panel_ids.includes(this.id))) {\n let anchor, scalar = null;\n if (_interaction.zooming && typeof this.x_scale == 'function') {\n const current_extent_size = Math.abs(this.x_extent[1] - this.x_extent[0]);\n const current_scaled_extent_size = Math.round(this.x_scale.invert(ranges.x_shifted[1])) - Math.round(this.x_scale.invert(ranges.x_shifted[0]));\n let zoom_factor = _interaction.zooming.scale;\n const potential_extent_size = Math.floor(current_scaled_extent_size * (1 / zoom_factor));\n if (zoom_factor < 1 && !isNaN(this.parent.layout.max_region_scale)) {\n zoom_factor = 1 / (Math.min(potential_extent_size, this.parent.layout.max_region_scale) / current_scaled_extent_size);\n } else if (zoom_factor > 1 && !isNaN(this.parent.layout.min_region_scale)) {\n zoom_factor = 1 / (Math.max(potential_extent_size, this.parent.layout.min_region_scale) / current_scaled_extent_size);\n }\n const new_extent_size = Math.floor(current_extent_size * zoom_factor);\n anchor = _interaction.zooming.center - margin.left - this.layout.origin.x;\n const offset_ratio = anchor / cliparea.width;\n const new_x_extent_start = Math.max(Math.floor(this.x_scale.invert(ranges.x_shifted[0]) - ((new_extent_size - current_scaled_extent_size) * offset_ratio)), 1);\n ranges.x_shifted = [ this.x_scale(new_x_extent_start), this.x_scale(new_x_extent_start + new_extent_size) ];\n } else if (current_drag) {\n switch (current_drag.method) {\n case 'background':\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n break;\n case 'x_tick':\n if (d3.event && d3.event.shiftKey) {\n ranges.x_shifted[0] = +current_drag.dragged_x;\n ranges.x_shifted[1] = cliparea.width + current_drag.dragged_x;\n } else {\n anchor = current_drag.start_x - margin.left - this.layout.origin.x;\n scalar = constrain(anchor / (anchor + current_drag.dragged_x), 3);\n ranges.x_shifted[0] = 0;\n ranges.x_shifted[1] = Math.max(cliparea.width * (1 / scalar), 1);\n }\n break;\n case 'y1_tick':\n case 'y2_tick': {\n const y_shifted = `y${current_drag.method[1]}_shifted`;\n if (d3.event && d3.event.shiftKey) {\n ranges[y_shifted][0] = cliparea.height + current_drag.dragged_y;\n ranges[y_shifted][1] = +current_drag.dragged_y;\n } else {\n anchor = cliparea.height - (current_drag.start_y - margin.top - this.layout.origin.y);\n scalar = constrain(anchor / (anchor - current_drag.dragged_y), 3);\n ranges[y_shifted][0] = cliparea.height;\n ranges[y_shifted][1] = cliparea.height - (cliparea.height * (1 / scalar));\n }\n }\n }\n }\n }\n\n // Generate scales and ticks for all axes, then render them\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (!this[`${axis}_extent`]) {\n return;\n }\n\n // Base Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`])\n .range(ranges[`${axis}_shifted`]);\n\n // Shift the extent\n this[`${axis}_extent`] = [\n this[`${axis}_scale`].invert(ranges[axis][0]),\n this[`${axis}_scale`].invert(ranges[axis][1]),\n ];\n\n // Finalize Scale\n this[`${axis}_scale`] = d3.scaleLinear()\n .domain(this[`${axis}_extent`]).range(ranges[axis]);\n\n // Render axis (and generate ticks as needed)\n this.renderAxis(axis);\n });\n\n // Establish mousewheel zoom event handers on the panel (namespacing not passed through by d3, so not used here)\n if (this.layout.interaction.scroll_to_zoom) {\n const zoom_handler = () => {\n // Look for a shift key press while scrolling to execute.\n // If not present, gracefully raise a notification and allow conventional scrolling\n if (!(d3.event.shiftKey || d3.event.altKey)) {\n if (this.parent._canInteract(this.id)) {\n this.loader.show('Press [SHIFT] or [ALT] while scrolling to zoom').hide(1000);\n }\n return;\n }\n d3.event.preventDefault();\n if (!this.parent._canInteract(this.id)) {\n return;\n }\n const coords = d3.mouse(this.svg.container.node());\n const delta = Math.max(-1, Math.min(1, (d3.event.wheelDelta || -d3.event.detail || -d3.event.deltaY)));\n if (delta === 0) {\n return;\n }\n this.parent._interaction = {\n panel_id: this.id,\n linked_panel_ids: this.getLinkedPanelIds('x'),\n zooming: {\n scale: (delta < 1) ? 0.9 : 1.1,\n center: coords[0],\n },\n };\n this.render();\n // Redefine b/c might have been changed during call to parent re-render\n _interaction = this.parent._interaction;\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.parent.panels[panel_id].render();\n });\n if (this._zoom_timeout !== null) {\n clearTimeout(this._zoom_timeout);\n }\n this._zoom_timeout = setTimeout(() => {\n this.parent._interaction = {};\n this.parent.applyState({ start: this.x_extent[0], end: this.x_extent[1] });\n }, 500);\n };\n // FIXME: Consider moving back to d3.zoom and rewriting drag + zoom to use behaviors.\n this.svg.container\n .on('wheel.zoom', zoom_handler)\n .on('mousewheel.zoom', zoom_handler)\n .on('DOMMouseScroll.zoom', zoom_handler);\n }\n\n // Render data layers in order by z-index\n this._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n this.data_layers[data_layer_id].draw().render();\n });\n\n // Rerender legend last (on top of data). A legend must have been defined at the start in order for this to work.\n if (this.legend) {\n this.legend.render();\n }\n return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel. This is rarely used directly: the `show_loading_indicator` panel layout\n * directive is the preferred way to trigger this function. The imperative form is useful if for some reason a\n * loading indicator needs to be added only after first render.\n * This method is just a shortcut for adding the most commonly used type of loading indicator, which appears when\n * data is requested, animates (e.g. shows an infinitely cycling progress bar as opposed to one that loads from\n * 0-100% based on actual load progress), and disappears when new data is loaded and rendered.\n *\n * @protected\n * @listens event:data_requested\n * @listens event:data_rendered\n * @param {Boolean} show_immediately\n * @returns {Panel}\n */\n addBasicLoader(show_immediately = true) {\n if (this.layout.show_loading_indicator && this._initialized) {\n // Prior to LZ 0.13, this function was called only after the plot was first rendered. Now, it is run by default.\n // Some older pages could thus end up adding a loader twice: to avoid duplicate render events,\n // short-circuit if a loader is already present after the first render has finished.\n return this;\n }\n if (show_immediately) {\n this.loader.show('Loading...').animate();\n }\n this.on('data_requested', () => {\n this.loader.show('Loading...').animate();\n });\n this.on('data_rendered', () => {\n this.loader.hide();\n });\n\n // Update layout to reflect new option\n this.layout.show_loading_indicator = true;\n return this;\n }\n\n /************* Private interface: only used internally */\n /** @private */\n applyDataLayerZIndexesToDataLayerLayouts () {\n this._data_layer_ids_by_z_index.forEach((dlid, idx) => {\n this.data_layers[dlid].layout.z_index = idx;\n });\n }\n\n /**\n * @private\n * @returns {string}\n */\n getBaseId () {\n return `${this.parent.id}.${this.id}`;\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const plot_origin = this.parent._getPageOrigin();\n return {\n x: plot_origin.x + this.layout.origin.x,\n y: plot_origin.y + this.layout.origin.y,\n };\n }\n\n /**\n * Prepare the panel for first use by performing parameter validation, creating axes, setting default dimensions,\n * and preparing / positioning data layers as appropriate.\n * @private\n * @returns {Panel}\n */\n initializeLayout() {\n // Set panel dimensions, origin, and margin\n this.setDimensions();\n this.setOrigin();\n this.setMargin();\n\n // Set ranges\n // TODO: Define stub values in constructor\n this.x_range = [0, this.layout.cliparea.width];\n this.y1_range = [this.layout.cliparea.height, 0];\n this.y2_range = [this.layout.cliparea.height, 0];\n\n // Initialize panel axes\n ['x', 'y1', 'y2'].forEach((id) => {\n const axis = this.layout.axes[id];\n if (!Object.keys(axis).length || axis.render === false) {\n // The default layout sets the axis to an empty object, so set its render boolean here\n axis.render = false;\n } else {\n axis.render = true;\n axis.label = axis.label || null;\n }\n });\n\n // Add data layers (which define x and y extents)\n this.layout.data_layers.forEach((data_layer_layout) => {\n this.addDataLayer(data_layer_layout);\n });\n\n return this;\n }\n\n /**\n * Set the dimensions for the panel. If passed with no arguments will calculate optimal size based on layout\n * directives and the available area within the plot. If passed discrete width (number) and height (number) will\n * attempt to resize the panel to them, but may be limited by minimum dimensions defined on the plot or panel.\n *\n * @private\n * @param {number} [width]\n * @param {number} [height]\n * @returns {Panel}\n */\n setDimensions(width, height) {\n const layout = this.layout;\n if (typeof width != 'undefined' && typeof height != 'undefined') {\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n this.parent.layout.width = Math.round(+width);\n // Ensure that the requested height satisfies all minimum values\n layout.height = Math.max(Math.round(+height), layout.min_height);\n }\n }\n layout.cliparea.width = Math.max(this.parent_plot.layout.width - (layout.margin.left + layout.margin.right), 0);\n layout.cliparea.height = Math.max(layout.height - (layout.margin.top + layout.margin.bottom), 0);\n if (this.svg.clipRect) {\n this.svg.clipRect\n .attr('width', this.parent.layout.width)\n .attr('height', layout.height);\n }\n if (this._initialized) {\n this.render();\n this.curtain.update();\n this.loader.update();\n this.toolbar.update();\n if (this.legend) {\n this.legend.position();\n }\n }\n return this;\n }\n\n /**\n * Set panel origin on the plot, and re-render as appropriate\n *\n * @private\n * @param {number} x\n * @param {number} y\n * @returns {Panel}\n */\n setOrigin(x, y) {\n if (!isNaN(x) && x >= 0) {\n this.layout.origin.x = Math.max(Math.round(+x), 0);\n }\n if (!isNaN(y) && y >= 0) {\n this.layout.origin.y = Math.max(Math.round(+y), 0);\n }\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Set margins around this panel\n * @private\n * @param {number} top\n * @param {number} right\n * @param {number} bottom\n * @param {number} left\n * @returns {Panel}\n */\n setMargin(top, right, bottom, left) {\n let extra;\n const { cliparea, margin } = this.layout;\n if (!isNaN(top) && top >= 0) {\n margin.top = Math.max(Math.round(+top), 0);\n }\n if (!isNaN(right) && right >= 0) {\n margin.right = Math.max(Math.round(+right), 0);\n }\n if (!isNaN(bottom) && bottom >= 0) {\n margin.bottom = Math.max(Math.round(+bottom), 0);\n }\n if (!isNaN(left) && left >= 0) {\n margin.left = Math.max(Math.round(+left), 0);\n }\n // If the specified margins are greater than the available width, then shrink the margins.\n if (margin.top + margin.bottom > this.layout.height) {\n extra = Math.floor(((margin.top + margin.bottom) - this.layout.height) / 2);\n margin.top -= extra;\n margin.bottom -= extra;\n }\n if (margin.left + margin.right > this.parent_plot.layout.width) {\n extra = Math.floor(((margin.left + margin.right) - this.parent_plot.layout.width) / 2);\n margin.left -= extra;\n margin.right -= extra;\n }\n ['top', 'right', 'bottom', 'left'].forEach((m) => {\n margin[m] = Math.max(margin[m], 0);\n });\n cliparea.width = Math.max(this.parent_plot.layout.width - (margin.left + margin.right), 0);\n cliparea.height = Math.max(this.layout.height - (margin.top + margin.bottom), 0);\n cliparea.origin.x = margin.left;\n cliparea.origin.y = margin.top;\n\n if (this._initialized) {\n this.render();\n }\n return this;\n }\n\n /**\n * Prepare the first rendering of the panel. This includes drawing the individual data layers, but also creates shared\n * elements such as axes, title, and loader/curtain.\n * @private\n * @returns {Panel}\n */\n initialize() {\n // Append a container group element to house the main panel group element and the clip path\n // Position with initial layout parameters\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.append('g')\n .attr('id', `${base_id}.panel_container`)\n .attr('transform', `translate(${this.layout.origin.x || 0}, ${this.layout.origin.y || 0})`);\n\n // Append clip path to the parent svg element, size with initial layout parameters\n const clipPath = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`);\n this.svg.clipRect = clipPath.append('rect')\n .attr('width', this.parent_plot.layout.width)\n .attr('height', this.layout.height);\n\n // Append svg group for rendering all panel child elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.panel`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n // Add curtain and loader to the panel\n /**\n * @protected\n * @member {Object}\n */\n this.curtain = generateCurtain.call(this);\n /**\n * @protected\n * @member {Object}\n */\n this.loader = generateLoader.call(this);\n\n if (this.layout.show_loading_indicator) {\n // Activate the loading indicator prior to first render, and only show when data is loading\n this.addBasicLoader(false);\n }\n\n /**\n * Create the toolbar object and hang widgets on it as defined by panel layout\n * @protected\n * @member {Toolbar}\n */\n this.toolbar = new Toolbar(this);\n\n // Inner border\n this.inner_border = this.svg.group.append('rect')\n .attr('class', 'lz-panel-background')\n .on('click', () => {\n if (this.layout.background_click === 'clear_selections') {\n this.clearSelections();\n }\n });\n\n // Add the title\n /**\n * @private\n * @member {Element}\n */\n this.title = this.svg.group.append('text').attr('class', 'lz-panel-title');\n if (typeof this.layout.title != 'undefined') {\n this.setTitle();\n }\n\n // Initialize Axes\n this.svg.x_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.x_axis`)\n .attr('class', 'lz-x lz-axis');\n if (this.layout.axes.x.render) {\n this.svg.x_axis_label = this.svg.x_axis.append('text')\n .attr('class', 'lz-x lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y1_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y1_axis`).attr('class', 'lz-y lz-y1 lz-axis');\n if (this.layout.axes.y1.render) {\n this.svg.y1_axis_label = this.svg.y1_axis.append('text')\n .attr('class', 'lz-y1 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n this.svg.y2_axis = this.svg.group.append('g')\n .attr('id', `${base_id}.y2_axis`)\n .attr('class', 'lz-y lz-y2 lz-axis');\n if (this.layout.axes.y2.render) {\n this.svg.y2_axis_label = this.svg.y2_axis.append('text')\n .attr('class', 'lz-y2 lz-axis lz-label')\n .attr('text-anchor', 'middle');\n }\n\n // Initialize child Data Layers\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].initialize();\n });\n\n /**\n * Legend object, as defined by panel layout and child data layer layouts\n * @protected\n * @member {Legend}\n * */\n this.legend = null;\n if (this.layout.legend) {\n this.legend = new Legend(this);\n }\n\n // Establish panel background drag interaction mousedown event handler (on the panel background)\n if (this.layout.interaction.drag_background_to_pan) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const mousedown = () => this.parent.startDrag(this, 'background');\n this.svg.container.select('.lz-panel-background')\n .on(`mousedown${namespace}.background`, mousedown)\n .on(`touchstart${namespace}.background`, mousedown);\n }\n\n return this;\n }\n\n /**\n * Refresh the sort order of all data layers (called by data layer moveForward and moveBack methods)\n * @private\n */\n resortDataLayers() {\n const sort = [];\n this._data_layer_ids_by_z_index.forEach((id) => {\n sort.push(this.data_layers[id].layout.z_index);\n });\n this.svg.group\n .selectAll('g.lz-data_layer-container')\n .data(sort)\n .sort(d3.ascending);\n this.applyDataLayerZIndexesToDataLayerLayouts();\n }\n\n /**\n * Get an array of panel IDs that are axis-linked to this panel\n * @private\n * @param {('x'|'y1'|'y2')} axis\n * @returns {Array}\n */\n getLinkedPanelIds(axis) {\n axis = axis || null;\n const linked_panel_ids = [];\n if (!['x', 'y1', 'y2'].includes(axis)) {\n return linked_panel_ids;\n }\n if (!this.layout.interaction[`${axis}_linked`]) {\n return linked_panel_ids;\n }\n this.parent._panel_ids_by_y_index.forEach((panel_id) => {\n if (panel_id !== this.id && this.parent.panels[panel_id].layout.interaction[`${axis}_linked`]) {\n linked_panel_ids.push(panel_id);\n }\n });\n return linked_panel_ids;\n }\n\n /**\n * Move a panel up relative to others by y-index\n * @private\n * @returns {Panel}\n */\n moveUp() {\n const { parent } = this;\n const y_index = this.layout.y_index;\n if (parent._panel_ids_by_y_index[y_index - 1]) {\n parent._panel_ids_by_y_index[y_index] = parent._panel_ids_by_y_index[y_index - 1];\n parent._panel_ids_by_y_index[y_index - 1] = this.id;\n parent.applyPanelYIndexesToPanelLayouts();\n parent.positionPanels();\n }\n return this;\n }\n\n /**\n * Move a panel down (y-axis) relative to others in the plot\n * @private\n * @returns {Panel}\n */\n moveDown() {\n const { _panel_ids_by_y_index } = this.parent;\n if (_panel_ids_by_y_index[this.layout.y_index + 1]) {\n _panel_ids_by_y_index[this.layout.y_index] = _panel_ids_by_y_index[this.layout.y_index + 1];\n _panel_ids_by_y_index[this.layout.y_index + 1] = this.id;\n this.parent.applyPanelYIndexesToPanelLayouts();\n this.parent.positionPanels();\n }\n return this;\n }\n\n /**\n * When the parent plot changes state, adjust the panel accordingly. For example, this may include fetching new data\n * from the API as the viewing region changes\n * @private\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @returns {Promise}\n */\n reMap() {\n this.emit('data_requested');\n this._data_promises = [];\n\n // Remove any previous error messages before attempting to load new data\n this.curtain.hide();\n // Trigger reMap on each Data Layer\n for (let id in this.data_layers) {\n try {\n this._data_promises.push(this.data_layers[id].reMap());\n } catch (error) {\n console.error(error);\n this.curtain.show(error.message || error);\n }\n }\n // When all finished trigger a render\n return Promise.all(this._data_promises)\n .then(() => {\n this._initialized = true;\n this.render();\n this.emit('layout_changed', true);\n this.emit('data_rendered');\n })\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n });\n }\n\n /**\n * Iterate over data layers to generate panel axis extents\n * @private\n * @returns {Panel}\n */\n generateExtents() {\n // Reset extents\n ['x', 'y1', 'y2'].forEach((axis) => {\n this[`${axis}_extent`] = null;\n });\n\n // Loop through the data layers\n for (let id in this.data_layers) {\n const data_layer = this.data_layers[id];\n\n // If defined and not decoupled, merge the x extent of the data layer with the panel's x extent\n if (data_layer.layout.x_axis && !data_layer.layout.x_axis.decoupled) {\n this.x_extent = d3.extent((this.x_extent || []).concat(data_layer.getAxisExtent('x')));\n }\n\n // If defined and not decoupled, merge the y extent of the data layer with the panel's appropriate y extent\n if (data_layer.layout.y_axis && !data_layer.layout.y_axis.decoupled) {\n const y_axis = `y${data_layer.layout.y_axis.axis}`;\n this[`${y_axis}_extent`] = d3.extent((this[`${y_axis}_extent`] || []).concat(data_layer.getAxisExtent('y')));\n }\n\n }\n\n // Override x_extent from state if explicitly defined to do so\n if (this.layout.axes.x && this.layout.axes.x.extent === 'state') {\n this.x_extent = [ this.state.start, this.state.end ];\n }\n return this;\n }\n\n /**\n * Generate an array of ticks for an axis. These ticks are generated in one of three ways (highest wins):\n * 1. An array of specific tick marks\n * 2. Query each data layer for what ticks are appropriate, and allow a panel-level tick configuration parameter\n * object to override the layer's default presentation settings\n * 3. Generate generic tick marks based on the extent of the data\n *\n * @private\n * @param {('x'|'y1'|'y2')} axis The string identifier of the axis\n * @returns {Number[]|Object[]} TODO: number format?\n * An array of numbers: interpreted as an array of axis value offsets for positioning.\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n generateTicks(axis) {\n // Parse an explicit 'ticks' attribute in the axis layout\n if (this.layout.axes[axis].ticks) {\n const layout = this.layout.axes[axis];\n\n const baseTickConfig = layout.ticks;\n if (Array.isArray(baseTickConfig)) {\n // Array of specific ticks hard-coded into a panel will override any ticks that an individual layer might specify\n return baseTickConfig;\n }\n\n if (typeof baseTickConfig === 'object') {\n // If the layout specifies base configuration for ticks- but without specific positions- then ask each\n // data layer to report the tick marks that it thinks it needs\n // TODO: Few layers currently need to specify custom ticks (which is ok!). But if it becomes common, consider adding mechanisms to deduplicate ticks across layers\n const self = this;\n\n // Pass any layer-specific customizations for how ticks are calculated. (styles are overridden separately)\n const config = { position: baseTickConfig.position };\n\n const combinedTicks = this._data_layer_ids_by_z_index.reduce((acc, data_layer_id) => {\n const nextLayer = self.data_layers[data_layer_id];\n return acc.concat(nextLayer.getTicks(axis, config));\n }, []);\n\n return combinedTicks.map((item) => {\n // The layer makes suggestions, but tick configuration params specified on the panel take precedence\n let itemConfig = {};\n itemConfig = merge(itemConfig, baseTickConfig);\n return merge(itemConfig, item);\n });\n }\n }\n\n // If no other configuration is provided, attempt to generate ticks from the extent\n if (this[`${axis}_extent`]) {\n return prettyTicks(this[`${axis}_extent`], 'both');\n }\n return [];\n }\n\n /**\n * Render ticks for a particular axis\n * @private\n * @param {('x'|'y1'|'y2')} axis The identifier of the axes\n * @returns {Panel}\n */\n renderAxis(axis) {\n if (!['x', 'y1', 'y2'].includes(axis)) {\n throw new Error(`Unable to render axis; invalid axis identifier: ${axis}`);\n }\n\n const canRender = this.layout.axes[axis].render\n && typeof this[`${axis}_scale`] == 'function'\n && !isNaN(this[`${axis}_scale`](0));\n\n // If the axis has already been rendered then check if we can/can't render it\n // Make sure the axis element is shown/hidden to suit\n if (this[`${axis}_axis`]) {\n this.svg.container.select(`g.lz-axis.lz-${axis}`)\n .style('display', canRender ? null : 'none');\n }\n\n if (!canRender) {\n return this;\n }\n\n // Axis-specific values to plug in where needed\n const axis_params = {\n x: {\n position: `translate(${this.layout.margin.left}, ${this.layout.height - this.layout.margin.bottom})`,\n orientation: 'bottom',\n label_x: this.layout.cliparea.width / 2,\n label_y: (this.layout.axes[axis].label_offset || 0),\n label_rotate: null,\n },\n y1: {\n position: `translate(${this.layout.margin.left}, ${this.layout.margin.top})`,\n orientation: 'left',\n label_x: -1 * (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n y2: {\n position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`,\n orientation: 'right',\n label_x: (this.layout.axes[axis].label_offset || 0),\n label_y: this.layout.cliparea.height / 2,\n label_rotate: -90,\n },\n };\n\n // Generate Ticks\n this[`${axis}_ticks`] = this.generateTicks(axis);\n\n // Determine if the ticks are all numbers (d3-automated tick rendering) or not (manual tick rendering)\n const ticksAreAllNumbers = ((ticks) => {\n for (let i = 0; i < ticks.length; i++) {\n if (isNaN(ticks[i])) {\n return false;\n }\n }\n return true;\n })(this[`${axis}_ticks`]);\n\n // Initialize the axis; set scale and orientation\n let axis_factory;\n switch (axis_params[axis].orientation) {\n case 'right':\n axis_factory = d3.axisRight;\n break;\n case 'left':\n axis_factory = d3.axisLeft;\n break;\n case 'bottom':\n axis_factory = d3.axisBottom;\n break;\n default:\n throw new Error('Unrecognized axis orientation');\n }\n\n this[`${axis}_axis`] = axis_factory(this[`${axis}_scale`])\n .tickPadding(3);\n\n // Set tick values and format\n if (ticksAreAllNumbers) {\n this[`${axis}_axis`].tickValues(this[`${axis}_ticks`]);\n if (this.layout.axes[axis].tick_format === 'region') {\n this[`${axis}_axis`].tickFormat((d) => positionIntToString(d, 6));\n }\n } else {\n let ticks = this[`${axis}_ticks`].map((t) => {\n return (t[axis.substr(0, 1)]);\n });\n this[`${axis}_axis`].tickValues(ticks)\n .tickFormat((t, i) => {\n return this[`${axis}_ticks`][i].text;\n });\n }\n\n // Position the axis in the SVG and apply the axis construct\n this.svg[`${axis}_axis`]\n .attr('transform', axis_params[axis].position)\n .call(this[`${axis}_axis`]);\n\n // If necessary manually apply styles and transforms to ticks as specified by the layout\n if (!ticksAreAllNumbers) {\n const tick_selector = d3.selectAll(`g#${this.getBaseId().replace('.', '\\\\.')}\\\\.${axis}_axis g.tick`);\n const panel = this;\n tick_selector.each(function (d, i) {\n const selector = d3.select(this).select('text');\n if (panel[`${axis}_ticks`][i].style) {\n applyStyles(selector, panel[`${axis}_ticks`][i].style);\n }\n if (panel[`${axis}_ticks`][i].transform) {\n selector.attr('transform', panel[`${axis}_ticks`][i].transform);\n }\n });\n }\n\n // Render the axis label if necessary\n const label = this.layout.axes[axis].label || null;\n if (label !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('x', axis_params[axis].label_x)\n .attr('y', axis_params[axis].label_y)\n .text(parseFields(label, this.state))\n .attr('fill', 'currentColor');\n if (axis_params[axis].label_rotate !== null) {\n this.svg[`${axis}_axis_label`]\n .attr('transform', `rotate(${axis_params[axis].label_rotate} ${axis_params[axis].label_x}, ${axis_params[axis].label_y})`);\n }\n }\n\n // Attach interactive handlers to ticks as needed\n ['x', 'y1', 'y2'].forEach((axis) => {\n if (this.layout.interaction[`drag_${axis}_ticks_to_scale`]) {\n const namespace = `.${this.parent.id}.${this.id}.interaction.drag`;\n const tick_mouseover = function() {\n if (typeof d3.select(this).node().focus == 'function') {\n d3.select(this).node().focus();\n }\n let cursor = (axis === 'x') ? 'ew-resize' : 'ns-resize';\n if (d3.event && d3.event.shiftKey) {\n cursor = 'move';\n }\n d3.select(this)\n .style('font-weight', 'bold')\n .style('cursor', cursor )\n .on(`keydown${namespace}`, tick_mouseover)\n .on(`keyup${namespace}`, tick_mouseover);\n };\n this.svg.container.selectAll(`.lz-axis.lz-${axis} .tick text`)\n .attr('tabindex', 0) // necessary to make the tick focusable so keypress events can be captured\n .on(`mouseover${namespace}`, tick_mouseover)\n .on(`mouseout${namespace}`, function() {\n d3.select(this)\n .style('font-weight', 'normal')\n .on(`keydown${namespace}`, null)\n .on(`keyup${namespace}`, null);\n })\n .on(`mousedown${namespace}`, () => {\n this.parent.startDrag(this, `${axis}_tick`);\n });\n }\n });\n\n return this;\n }\n\n /**\n * Force the height of this panel to the largest absolute height of the data in\n * all child data layers (if not null for any child data layers)\n * @private\n * @param {number|null} [target_height] A target height, which will be used in situations when the expected height can be\n * pre-calculated (eg when the layers are transitioning)\n */\n scaleHeightToData(target_height) {\n target_height = +target_height || null;\n if (target_height === null) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n const dh = this.data_layers[id].getAbsoluteDataHeight();\n if (+dh) {\n if (target_height === null) {\n target_height = +dh;\n } else {\n target_height = Math.max(target_height, +dh);\n }\n }\n });\n }\n if (+target_height) {\n target_height += +this.layout.margin.top + +this.layout.margin.bottom;\n // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments)\n this.setDimensions(this.parent_plot.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.positionPanels();\n }\n }\n\n /**\n * Set/unset element statuses across all data layers\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n */\n setAllElementStatus(status, toggle) {\n this._data_layer_ids_by_z_index.forEach((id) => {\n this.data_layers[id].setAllElementStatus(status, toggle);\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n\n // Set/unset status for all elements\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n Panel.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n */\n /**\n * @private\n * @function unhideAllElements\n */\n Panel.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {Panel as default};\n","/**\n * Helpers that control the display of individual points and field values\n * @module\n * @private\n */\nimport * as d3 from 'd3';\n\nimport Field from '../data/field';\nimport Plot from '../components/plot';\nimport {applyStyles} from './common';\n\n\n/**\n * Convert an integer chromosome position to an SI string representation (e.g. 23423456 => \"23.42\" (Mb))\n * @param {Number} pos Position\n * @param {Number} [exp] Exponent to use for the returned string, eg 6=> MB. If not specified, will attempt to guess\n * the most appropriate SI prefix based on the number provided.\n * @param {Boolean} [suffix=false] Whether or not to append a suffix (e.g. \"Mb\") to the end of the returned string\n * @returns {string}\n */\nfunction positionIntToString(pos, exp, suffix) {\n const exp_symbols = { 0: '', 3: 'K', 6: 'M', 9: 'G' };\n suffix = suffix || false;\n if (isNaN(exp) || exp === null) {\n const log = Math.log(pos) / Math.LN10;\n exp = Math.min(Math.max(log - (log % 3), 0), 9);\n }\n const places_exp = exp - Math.floor((Math.log(pos) / Math.LN10).toFixed(exp + 3));\n const min_exp = Math.min(Math.max(exp, 0), 2);\n const places = Math.min(Math.max(places_exp, min_exp), 12);\n let ret = `${(pos / Math.pow(10, exp)).toFixed(places)}`;\n if (suffix && typeof exp_symbols[exp] !== 'undefined') {\n ret += ` ${exp_symbols[exp]}b`;\n }\n return ret;\n}\n\n/**\n * Convert an SI string chromosome position to an integer representation (e.g. \"5.8 Mb\" => 58000000)\n * @param {String} p The chromosome position\n * @returns {Number}\n */\nfunction positionStringToInt(p) {\n let val = p.toUpperCase();\n val = val.replace(/,/g, '');\n const suffixre = /([KMG])[B]*$/;\n const suffix = suffixre.exec(val);\n let mult = 1;\n if (suffix) {\n if (suffix[1] === 'M') {\n mult = 1e6;\n } else if (suffix[1] === 'G') {\n mult = 1e9;\n } else {\n mult = 1e3; //K\n }\n val = val.replace(suffixre, '');\n }\n val = Number(val) * mult;\n return val;\n}\n\n/**\n * Generate a \"pretty\" set of ticks (multiples of 1, 2, or 5 on the same order of magnitude for the range)\n * Based on R's \"pretty\" function: https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/appl/pretty.c\n * @param {Number[]} range A two-item array specifying [low, high] values for the axis range\n * @param {('low'|'high'|'both'|'neither')} [clip_range='neither'] What to do if first and last generated ticks extend\n * beyond the range. Set this to \"low\", \"high\", \"both\", or \"neither\" to clip the first (low) or last (high) tick to\n * be inside the range or allow them to extend beyond.\n * e.g. \"low\" will clip the first (low) tick if it extends beyond the low end of the range but allow the\n * last (high) tick to extend beyond the range. \"both\" clips both ends, \"neither\" allows both to extend beyond.\n * @param {Number} [target_tick_count=5] The approximate number of ticks you would like to be returned; may not be exact\n * @returns {Number[]}\n */\nfunction prettyTicks(range, clip_range, target_tick_count) {\n if (typeof target_tick_count == 'undefined' || isNaN(parseInt(target_tick_count))) {\n target_tick_count = 5;\n }\n target_tick_count = +target_tick_count;\n\n const min_n = target_tick_count / 3;\n const shrink_sml = 0.75;\n const high_u_bias = 1.5;\n const u5_bias = 0.5 + 1.5 * high_u_bias;\n\n const d = Math.abs(range[0] - range[1]);\n let c = d / target_tick_count;\n if ((Math.log(d) / Math.LN10) < -2) {\n c = (Math.max(Math.abs(d)) * shrink_sml) / min_n;\n }\n\n const base = Math.pow(10, Math.floor(Math.log(c) / Math.LN10));\n let base_toFixed = 0;\n if (base < 1 && base !== 0) {\n base_toFixed = Math.abs(Math.round(Math.log(base) / Math.LN10));\n }\n\n let unit = base;\n if ( ((2 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 2 * base;\n if ( ((5 * base) - c) < (u5_bias * (c - unit)) ) {\n unit = 5 * base;\n if ( ((10 * base) - c) < (high_u_bias * (c - unit)) ) {\n unit = 10 * base;\n }\n }\n }\n\n let ticks = [];\n let i = parseFloat((Math.floor(range[0] / unit) * unit).toFixed(base_toFixed));\n while (i < range[1]) {\n ticks.push(i);\n i += unit;\n if (base_toFixed > 0) {\n i = parseFloat(i.toFixed(base_toFixed));\n }\n }\n ticks.push(i);\n\n if (typeof clip_range == 'undefined' || ['low', 'high', 'both', 'neither'].indexOf(clip_range) === -1) {\n clip_range = 'neither';\n }\n if (clip_range === 'low' || clip_range === 'both') {\n if (ticks[0] < range[0]) {\n ticks = ticks.slice(1);\n }\n }\n if (clip_range === 'high' || clip_range === 'both') {\n if (ticks[ticks.length - 1] > range[1]) {\n ticks.pop();\n }\n }\n\n return ticks;\n}\n\n/**\n * Replace placeholders in an html string with field values defined in a data object\n * Only works on scalar values in data! Will ignore non-scalars. This is useful in, eg, tooltip templates.\n *\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\n * @param {String} html A placeholder string in which to substitute fields. Supports several template options:\n * `{{field_name}}` is a variable placeholder for the value of `field_name` from the provided data\n * `{{#if field_name}} Conditional text {{/if}}` will insert the contents of the tag only if the value exists.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved. For numbers, transforms like `{{#if field|is_numeric}}` can help to ensure that 0\n * values are displayed when expected.\n * Can optionally take an else block, useful for things like toggle buttons: {{#if field}} ... {{#else}} ... {{/if}}\n * @param {Object} data The data associated with a particular element. Eg, tooltips often appear over a specific point.\n * @param {Object|null} extra Any additional fields (eg element annotations) associated with the specified datum\n * @returns {string}\n */\nfunction parseFields(html, data, extra) {\n if (typeof data != 'object') {\n throw new Error('invalid arguments: data is not an object');\n }\n if (typeof html != 'string') {\n throw new Error('invalid arguments: html is not a string');\n }\n // `tokens` is like [token,...]\n // `token` is like {text: '...'} or {variable: 'foo|bar'} or {condition: 'foo|bar'} or {close: 'if'}\n const tokens = [];\n const regex = /{{(?:(#if )?([\\w+_:|]+)|(#else)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html});\n html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)});\n html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]});\n html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]});\n html = html.slice(m[0].length);\n } else if (m[3] === '#else') {\n tokens.push({branch: 'else'});\n html = html.slice(m[0].length);\n } else if (m[4] === '/if') {\n tokens.push({close: 'if'});\n html = html.slice(m[0].length);\n } else {\n console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(html)} and previous tokens are ${JSON.stringify(tokens)} and current regex match is ${JSON.stringify([m[1], m[2], m[3]])}`);\n html = html.slice(m[0].length);\n }\n }\n const astify = function () {\n const token = tokens.shift();\n if (typeof token.text !== 'undefined' || token.variable) {\n return token;\n } else if (token.condition) {\n let dest = token.then = [];\n token.else = [];\n // Inside an if block, consume all tokens related to text and/or else block\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n if (tokens[0].branch === 'else') {\n tokens.shift();\n dest = token.else;\n }\n dest.push(astify());\n }\n return token;\n } else {\n console.error(`Error making tooltip AST due to unknown token ${JSON.stringify(token)}`);\n return { text: '' };\n }\n };\n // `ast` is like [thing,...]\n // `thing` is like {text: \"...\"} or {variable:\"foo|bar\"} or {condition: \"foo|bar\", then:[thing,...]}\n const ast = [];\n while (tokens.length > 0) {\n ast.push(astify());\n }\n\n const resolve = function (variable) {\n if (!Object.prototype.hasOwnProperty.call(resolve.cache, variable)) {\n resolve.cache[variable] = (new Field(variable)).resolve(data, extra);\n }\n return resolve.cache[variable];\n };\n resolve.cache = {};\n const render_node = function (node) {\n if (typeof node.text !== 'undefined') {\n return node.text;\n } else if (node.variable) {\n try {\n const value = resolve(node.variable);\n if (['string', 'number', 'boolean'].indexOf(typeof value) !== -1) {\n return value;\n }\n if (value === null) {\n return '';\n }\n } catch (error) {\n console.error(`Error while processing variable ${JSON.stringify(node.variable)}`);\n }\n return `{{${node.variable}}}`;\n } else if (node.condition) {\n try {\n const condition = resolve(node.condition);\n if (condition) {\n return node.then.map(render_node).join('');\n } else if (node.else) {\n return node.else.map(render_node).join('');\n }\n } catch (error) {\n console.error(`Error while processing condition ${JSON.stringify(node.variable)}`);\n }\n return '';\n } else {\n console.error(`Error rendering tooltip due to unknown AST node ${JSON.stringify(node)}`);\n }\n };\n return ast.map(render_node).join('');\n}\n\n/**\n * Populate a single element with a LocusZoom plot. This is the primary means of generating a new plot, and is part\n * of the public interface for LocusZoom.\n * @alias module:LocusZoom~populate\n * @public\n * @param {String|d3.selection} selector CSS selector for the container element where the plot will be mounted. Any pre-existing\n * content in the container will be completely replaced.\n * @param {module:LocusZoom~DataSources} datasource Ensemble of data providers used by the plot\n * @param {Object} layout A JSON-serializable object of layout configuration parameters\n * @returns {Plot} The newly created plot instance\n */\nfunction populate(selector, datasource, layout) {\n if (typeof selector == 'undefined') {\n throw new Error('LocusZoom.populate selector not defined');\n }\n // Empty the selector of any existing content\n d3.select(selector).html('');\n let plot;\n d3.select(selector).call(function(target) {\n // Require each containing element have an ID. If one isn't present, create one.\n if (typeof target.node().id == 'undefined') {\n let iterator = 0;\n while (!d3.select(`#lz-${iterator}`).empty()) {\n iterator++;\n }\n target.attr('id', `#lz-${iterator}`);\n }\n // Create the plot\n plot = new Plot(target.node().id, datasource, layout);\n plot.container = target.node();\n // Detect HTML `data-region` attribute, and use it to fill in state values if present\n if (typeof target.node().dataset !== 'undefined' && typeof target.node().dataset.region !== 'undefined') {\n const parsed_state = parsePositionQuery(target.node().dataset.region);\n Object.keys(parsed_state).forEach(function(key) {\n plot.state[key] = parsed_state[key];\n });\n }\n // Add an SVG to the div and set its dimensions\n plot.svg = d3.select(`div#${plot.id}`)\n .append('svg')\n .attr('version', '1.1')\n .attr('xmlns', 'http://www.w3.org/2000/svg')\n .attr('id', `${plot.id}_svg`)\n .attr('class', 'lz-locuszoom')\n .call(applyStyles, plot.layout.style);\n\n plot.setDimensions();\n plot.positionPanels();\n // Initialize the plot\n plot.initialize();\n // If the plot has defined data sources then trigger its first mapping based on state values\n if (datasource) {\n plot.refresh();\n }\n });\n return plot;\n}\n\n/**\n * Parse region queries into their constituent parts\n * @param {String} x A chromosome position query. May be any of the forms `chr:start-end`, `chr:center+offset`,\n * or `chr:pos`\n * @returns {{chr:*, start: *, end:*} | {chr:*, position:*}}\n */\nfunction parsePositionQuery(x) {\n const chrposoff = /^(\\w+):([\\d,.]+[kmgbKMGB]*)([-+])([\\d,.]+[kmgbKMGB]*)$/;\n const chrpos = /^(\\w+):([\\d,.]+[kmgbKMGB]*)$/;\n let match = chrposoff.exec(x);\n if (match) {\n if (match[3] === '+') {\n const center = positionStringToInt(match[2]);\n const offset = positionStringToInt(match[4]);\n return {\n chr:match[1],\n start: center - offset,\n end: center + offset,\n };\n } else {\n return {\n chr: match[1],\n start: positionStringToInt(match[2]),\n end: positionStringToInt(match[4]),\n };\n }\n }\n match = chrpos.exec(x);\n if (match) {\n return {\n chr:match[1],\n position: positionStringToInt(match[2]),\n };\n }\n return null;\n}\n\nexport { parseFields, parsePositionQuery, populate, positionIntToString, positionStringToInt, prettyTicks };\n","import * as d3 from 'd3';\n\nimport {deepCopy, merge} from '../helpers/layouts';\nimport Requester from '../data/requester';\nimport Toolbar from './toolbar';\nimport Panel from './panel';\nimport {generateCurtain, generateLoader} from '../helpers/common';\n\n/**\n * Default/ expected configuration parameters for basic plotting; most plots will override\n *\n * @memberof Plot\n * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n min_region_scale: null,\n max_region_scale: null,\n responsive_resize: false,\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\n};\n\n\n/**\n * Fields common to every event emitted by LocusZoom. This is not an actual event that should ever be used directly;\n * see list below.\n *\n * Note: plot-level listeners *can* be defined for this event, but you should almost never do this.\n * Use the most specific event name to describe the thing you are interested in.\n *\n * Listening to 'any_lz_event' is only for advanced usages, such as proxying (repeating) LZ behavior to a piece of\n * wrapper code. One example is converting all LocusZoom events to vue.js events.\n *\n * @event any_lz_event\n * @type {object}\n * @property {string} sourceID The fully qualified ID of the entity that originated the event, eg `lz-plot.association`\n * @property {Plot|Panel} target A reference to the plot or panel instance that originated the event.\n * @property {object|null} data Additional data provided. (see event-specific documentation)\n */\n\n/**\n * A panel was removed from the plot. Commonly initiated by the \"remove panel\" toolbar widget.\n * @event panel_removed\n * @property {string} data The id of the panel that was removed (eg 'genes')\n * @see event:any_lz_event\n */\n\n/**\n * A request for new or cached data was initiated. This can be used for, eg, showing data loading indicators.\n * @event data_requested\n * @see event:any_lz_event\n */\n\n/**\n * A request for new data has completed, and all data has been rendered in the plot.\n * @event data_rendered\n * @see event:any_lz_event\n */\n\n/**\n * One particular data layer has completed a request for data. This event is primarily used internally by the `subscribeToData` function, and the syntax may change in the future.\n * @event data_from_layer\n * @property {object} data\n * @property {String} data.layer The fully qualified ID of the layer emitting this event\n * @property {Object[]} data.content The data used to draw this layer: an array where each element represents one row/ datum\n * element. It reflects all namespaces and data operations used by that layer.\n * @see event:any_lz_event\n */\n\n\n/**\n * An action occurred that changed, or could change, the layout.\n * Many rerendering operations can fire this event and it is somewhat generic: it includes resize, highlight,\n * and rerender on new data.\n * Caution: Direct layout mutations might not be captured by this event. It is deprecated due to its limited utility.\n * @event layout_changed\n * @deprecated\n * @see event:any_lz_event\n */\n\n/**\n * The user has requested any state changes, eg via `plot.applyState`. This reports the original requested values even\n * if they are overridden by plot logic. Only triggered when a state change causes a re-render.\n * @event state_changed\n * @property {object} data The set of all state changes requested\n * @see event:any_lz_event\n * @see {@link event:region_changed} for a related event that provides more accurate information in some cases\n */\n\n/**\n * The plot region has changed. Reports the actual coordinates of the plot after the zoom event. If plot.applyState is\n * called with an invalid region (eg zooming in or out too far), this reports the actual final coordinates, not what was requested.\n * The actual coordinates are subject to region min/max, etc.\n * @event region_changed\n * @property {object} data The {chr, start, end} coordinates of the requested region.\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether the element was selected (or unselected)\n * @event element_selection\n * @property {object} data An object with keys { element, active }, representing the datum bound to the element and the\n * selection status (boolean)\n * @see {@link event:element_clicked} if you are interested in tracking clicks that result in other behaviors, like links\n * @see event:any_lz_event\n */\n\n/**\n * Indicates whether an element was clicked. (regardless of the behavior associated with clicking)\n * @event element_clicked\n * @see {@link event:element_selection} for a more specific and more frequently useful event\n * @see event:any_lz_event\n */\n\n/**\n * Indicate whether a match was requested from within a data layer.\n * @event match_requested\n * @property {object} data An object of `{value, active}` representing the scalar value to be matched and whether a match is\n * being initiated or canceled\n * @see event:any_lz_event\n */\n\n/**\n * Check that position fields (chr, start, end) are provided where appropriate, and ensure that the plot fits within\n * any constraints specified by the layout\n *\n * This function has side effects; it mutates the proposed state in order to meet certain bounds checks etc.\n * @private\n * @param {Object} new_state\n * @param {Number} new_state.chr\n * @param {Number} new_state.start\n * @param {Number} new_state.end\n * @param {Object} layout\n * @returns {*|{}}\n */\nfunction _updateStatePosition(new_state, layout) {\n\n new_state = new_state || {};\n layout = layout || {};\n\n // If a \"chr\", \"start\", and \"end\" are present then resolve start and end\n // to numeric values that are not decimal, negative, or flipped\n let validated_region = false;\n let attempted_midpoint = null;\n let attempted_scale;\n if (typeof new_state.chr != 'undefined' && typeof new_state.start != 'undefined' && typeof new_state.end != 'undefined') {\n // Determine a numeric scale and midpoint for the attempted region,\n new_state.start = Math.max(parseInt(new_state.start), 1);\n new_state.end = Math.max(parseInt(new_state.end), 1);\n if (isNaN(new_state.start) && isNaN(new_state.end)) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_midpoint = 0.5;\n attempted_scale = 0;\n } else if (isNaN(new_state.start) || isNaN(new_state.end)) {\n attempted_midpoint = new_state.start || new_state.end;\n attempted_scale = 0;\n new_state.start = (isNaN(new_state.start) ? new_state.end : new_state.start);\n new_state.end = (isNaN(new_state.end) ? new_state.start : new_state.end);\n } else {\n attempted_midpoint = Math.round((new_state.start + new_state.end) / 2);\n attempted_scale = new_state.end - new_state.start;\n if (attempted_scale < 0) {\n const temp = new_state.start;\n new_state.end = new_state.start;\n new_state.start = temp;\n attempted_scale = new_state.end - new_state.start;\n }\n if (attempted_midpoint < 0) {\n new_state.start = 1;\n new_state.end = 1;\n attempted_scale = 0;\n }\n }\n validated_region = true;\n }\n\n // Constrain w/r/t layout-defined minimum region scale\n if (layout.min_region_scale && validated_region && attempted_scale < layout.min_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.min_region_scale / 2), 1);\n new_state.end = new_state.start + layout.min_region_scale;\n }\n\n // Constrain w/r/t layout-defined maximum region scale\n if (layout.max_region_scale && validated_region && attempted_scale > layout.max_region_scale) {\n new_state.start = Math.max(attempted_midpoint - Math.floor(layout.max_region_scale / 2), 1);\n new_state.end = new_state.start + layout.max_region_scale;\n }\n\n return new_state;\n}\n\n\nclass Plot {\n /**\n * An independent LocusZoom object that renders a unique set of data and subpanels.\n * Many such LocusZoom objects can exist simultaneously on a single page, each having its own layout.\n *\n * This creates a new plot instance, but does not immediately render it. For practical use, it may be more convenient\n * to use the `LocusZoom.populate` helper method.\n *\n * @param {String} id The ID of the plot. Often corresponds to the ID of the container element on the page\n * where the plot is rendered..\n * @param {DataSources} datasource Ensemble of data providers used by the plot\n * @param {object} [layout.state] Initial state parameters; the most common options are 'chr', 'start', and 'end'\n * to specify initial region view\n * @param {number} [layout.width=800] The width of the plot and all child panels\n * @param {number} [layout.min_width=400] Do not allow the panel to be resized below this width\n * @param {number} [layout.min_region_scale] The minimum region width (do not allow the user to zoom smaller than this region size)\n * @param {number} [layout.max_region_scale] The maximum region width (do not allow the user to zoom wider than this region size)\n * @param {boolean} [layout.responsive_resize=false] Whether to resize plot width as the screen is resized\n * @param {Object[]} [layout.panels] Configuration options for each panel to be added\n * @param {module:LocusZoom_Widgets[]} [layout.toolbar.widgets] Configuration options for each widget to place on the\n * plot-level toolbar\n * @param {boolean} [layout.panel_boundaries=true] Whether to show interactive resize handles to change panel dimensions\n * @param {boolean} [layout.mouse_guide=true] Whether to always show horizontal and vertical dotted lines that intersect at the current location of the mouse pointer.\n * This line spans the entire plot area and is especially useful for plots with multiple panels.\n */\n constructor(id, datasource, layout) {\n /**\n * @private\n * @member Boolean}\n */\n this._initialized = false;\n\n /**\n * @private\n * @member {Plot}\n */\n this.parent_plot = this;\n\n /**\n * @public\n * @member {String}\n */\n this.id = id;\n\n /**\n * @private\n * @member {Element}\n */\n this.container = null;\n /**\n * Selector for a node that will contain the plot. (set externally by populate methods)\n * @private\n * @member {d3.selection}\n */\n this.svg = null;\n\n /**\n * Direct access to panel instances, keyed by panel ID. Used primarily for introspection/ development.\n * @public\n * @member {Object.}\n */\n this.panels = {};\n /**\n * TODO: This is currently used by external classes that manipulate the parent and may indicate room for a helper method in the api to coordinate boilerplate\n * @private\n * @member {String[]}\n */\n this._panel_ids_by_y_index = [];\n\n /**\n * Track update operations (reMap) performed on all child panels, and notify the parent plot when complete\n * TODO: Reconsider whether we need to be tracking this as global state outside of context of specific operations\n * @ignore\n * @protected\n * @member {Promise[]}\n */\n this._remap_promises = [];\n\n\n /**\n * The current layout options for the plot, including the effect of any resizing events or dynamically\n * generated config produced during rendering options.\n * @public\n * @type {Object}\n */\n this.layout = layout;\n merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original plot options.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * Create a shortcut to the state in the layout on the Plot. Tracking in the layout allows the plot to be created\n * with initial state/setup.\n *\n * Tracks state of the plot, eg start and end position\n * @public\n * @member {Object}\n */\n this.state = this.layout.state;\n\n /**\n * @private\n * @member {Requester}\n */\n this.lzd = new Requester(datasource);\n\n /**\n * Track global event listeners that are used by LZ. This allows cleanup of listeners when plot is destroyed.\n * @private\n * @member {Map} A nested hash of entries: { parent: {event_name: [listeners] } }\n */\n this._external_listeners = new Map();\n\n /**\n * Known event hooks that the panel can respond to\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @protected\n * @member {Object}\n */\n this._event_hooks = {};\n\n /**\n * @callback eventCallback\n * @param {object} eventData A description of the event\n * @param {String|null} eventData.sourceID The unique identifier (eg plot or parent name) of the element that\n * triggered the event. Will be automatically filled in if not explicitly provided.\n * @param {Object|null} eventData.context Any additional information to be passed to the callback, eg the data\n * associated with a clicked plot element\n */\n\n /**\n * Event information describing interaction (e.g. panning and zooming) is stored on the plot\n * TODO: Add/ document details of interaction structure as we expand\n * @private\n * @member {{panel_id: String, linked_panel_ids: Array, x_linked: *, dragging: *, zooming: *}}\n * @returns {Plot}\n */\n this._interaction = {};\n\n // Initialize the layout\n this.initializeLayout();\n }\n\n /******* User-facing methods that allow manipulation of the plot instance: the public interface */\n\n /**\n * There are several events that a LocusZoom plot can \"emit\" when appropriate, and LocusZoom supports registering\n * \"hooks\" for these events which are essentially custom functions intended to fire at certain times.\n *\n * To register a hook for any of these events use `plot.on('event_name', function() {})`.\n *\n * There can be arbitrarily many functions registered to the same event. They will be executed in the order they\n * were registered.\n *\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event. Consult documentation for the names of built-in events.\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof event !== 'string') {\n throw new Error(`Unable to register event hook. Event name must be a string: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\n }\n if (!this._event_hooks[event]) {\n // We do not validate on known event names, because LZ is allowed to track and emit custom events like \"widget button clicked\".\n this._event_hooks[event] = [];\n }\n this._event_hooks[event].push(hook);\n return hook;\n }\n\n /**\n * Remove one or more previously defined event listeners\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} [hook] The callback to deregister\n * @returns {Plot}\n */\n off(event, hook) {\n const theseHooks = this._event_hooks[event];\n if (typeof event != 'string' || !Array.isArray(theseHooks)) {\n throw new Error(`Unable to remove event hook, invalid event: ${event.toString()}`);\n }\n if (hook === undefined) {\n // Deregistering all hooks for this event may break basic functionality, and should only be used during\n // cleanup operations (eg to prevent memory leaks)\n this._event_hooks[event] = [];\n } else {\n const hookMatch = theseHooks.indexOf(hook);\n if (hookMatch !== -1) {\n theseHooks.splice(hookMatch, 1);\n } else {\n throw new Error('The specified event listener is not registered and therefore cannot be removed');\n }\n }\n return this;\n }\n\n /**\n * Handle running of event hooks when an event is emitted\n * @public\n * @see {@link event:any_lz_event} for a list of pre-defined events commonly used by LocusZoom\n * @param {string} event A known event name\n * @param {*} eventData Data or event description that will be passed to the event listener\n * @returns {Plot}\n */\n emit(event, eventData) {\n // TODO: there are small differences between the emit implementation between plots and panels. In the future,\n // DRY this code via mixins, and make sure to keep the interfaces compatible when refactoring.\n const these_hooks = this._event_hooks[event];\n if (typeof event != 'string') {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n } else if (!these_hooks && !this._event_hooks['any_lz_event']) {\n // If the tree_fall event is emitted in a forest and no one is around to hear it, does it really make a sound?\n return this;\n }\n const sourceID = this.getBaseId();\n let eventContext;\n if (eventData && eventData.sourceID) {\n // If we detect that an event originated elsewhere (via bubbling or externally), preserve the context\n // when re-emitting the event to plot-level listeners\n eventContext = eventData;\n } else {\n eventContext = {sourceID: sourceID, target: this, data: eventData || null};\n }\n if (these_hooks) {\n // This event may have no hooks, but we could be passing by on our way to any_lz_event (below)\n these_hooks.forEach((hookToRun) => {\n // By default, any handlers fired here (either directly, or bubbled) will see the plot as the\n // value of `this`. If a bound function is registered as a handler, the previously bound `this` will\n // override anything provided to `call` below.\n hookToRun.call(this, eventContext);\n });\n }\n\n // At the plot level (only), all events will be re-emitted under the special name \"any_lz_event\"- a single place to\n // globally listen to every possible event.\n // This is not intended for direct use. It is for UI frameworks like Vue.js, which may need to wrap LZ\n // instances and proxy all events to their own declarative event system\n if (event !== 'any_lz_event') {\n const anyEventData = Object.assign({ event_name: event }, eventContext);\n this.emit('any_lz_event', anyEventData);\n }\n return this;\n }\n\n /**\n * Create a new panel from a layout, and handle the work of initializing and placing the panel on the plot\n * @public\n * @param {Object} layout\n * @returns {Panel}\n */\n addPanel(layout) {\n // Sanity checks\n if (typeof layout !== 'object') {\n throw new Error('Invalid panel layout');\n }\n\n // Create the Panel and set its parent\n const panel = new Panel(layout, this);\n\n // Store the Panel on the Plot\n this.panels[panel.id] = panel;\n\n // If a discrete y_index was set in the layout then adjust other panel y_index values to accommodate this one\n if (panel.layout.y_index !== null && !isNaN(panel.layout.y_index)\n && this._panel_ids_by_y_index.length > 0) {\n // Negative y_index values should count backwards from the end, so convert negatives to appropriate values here\n if (panel.layout.y_index < 0) {\n panel.layout.y_index = Math.max(this._panel_ids_by_y_index.length + panel.layout.y_index, 0);\n }\n this._panel_ids_by_y_index.splice(panel.layout.y_index, 0, panel.id);\n this.applyPanelYIndexesToPanelLayouts();\n } else {\n const length = this._panel_ids_by_y_index.push(panel.id);\n this.panels[panel.id].layout.y_index = length - 1;\n }\n\n // Determine if this panel was already in the layout.panels array.\n // If it wasn't, add it. Either way store the layout.panels array index on the panel.\n let layout_idx = null;\n this.layout.panels.forEach((panel_layout, idx) => {\n if (panel_layout.id === panel.id) {\n layout_idx = idx;\n }\n });\n if (layout_idx === null) {\n layout_idx = this.layout.panels.push(this.panels[panel.id].layout) - 1;\n }\n this.panels[panel.id]._layout_idx = layout_idx;\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // Initialize and load data into the new panel\n this.panels[panel.id].initialize();\n this.panels[panel.id].reMap();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n return this.panels[panel.id];\n }\n\n /**\n * Clear all state, tooltips, and other persisted data associated with one (or all) panel(s) in the plot\n *\n * This is useful when reloading an existing plot with new data, eg \"click for genome region\" links.\n * This is a utility method for custom usage. It is not fired automatically during normal rerender of existing panels\n * TODO: Is this method still necessary in modern usage? Hide from docs for now.\n * @public\n * @ignore\n * @param {String} [panelId] If provided, clear state for only this panel. Otherwise, clear state for all panels.\n * @param {('wipe'|'reset')} [mode='wipe'] Optionally specify how state should be cleared. `wipe` deletes all data\n * and is useful for when the panel is being removed; `reset` is best when the panel will be reused in place.\n * @returns {Plot}\n */\n clearPanelData(panelId, mode) {\n mode = mode || 'wipe';\n\n // TODO: Add unit tests for this method\n let panelsList;\n if (panelId) {\n panelsList = [panelId];\n } else {\n panelsList = Object.keys(this.panels);\n }\n\n panelsList.forEach((pid) => {\n this.panels[pid]._data_layer_ids_by_z_index.forEach((dlid) => {\n const layer = this.panels[pid].data_layers[dlid];\n layer.destroyAllTooltips();\n\n delete layer._layer_state;\n delete this.layout.state[layer._state_id];\n if (mode === 'reset') {\n layer._setDefaultState();\n }\n });\n });\n return this;\n }\n\n /**\n * Remove the panel from the plot, and clear any state, tooltips, or other visual elements belonging to nested content\n * @public\n * @fires event:panel_removed\n * @param {String} id\n * @returns {Plot}\n */\n removePanel(id) {\n const target_panel = this.panels[id];\n if (!target_panel) {\n throw new Error(`Unable to remove panel, ID not found: ${id}`);\n }\n\n // Hide all panel boundaries\n this._panel_boundaries.hide();\n\n // Destroy all tooltips and state vars for all data layers on the panel\n this.clearPanelData(id);\n\n // Remove all panel-level HTML overlay elements\n target_panel.loader.hide();\n target_panel.toolbar.destroy(true);\n target_panel.curtain.hide();\n\n // Remove the svg container for the panel if it exists\n if (target_panel.svg.container) {\n target_panel.svg.container.remove();\n }\n\n // Delete the panel and its presence in the plot layout and state\n this.layout.panels.splice(target_panel._layout_idx, 1);\n delete this.panels[id];\n delete this.layout.state[id];\n\n // Update layout_idx values for all remaining panels\n this.layout.panels.forEach((panel_layout, idx) => {\n this.panels[panel_layout.id]._layout_idx = idx;\n });\n\n // Remove the panel id from the y_index array\n this._panel_ids_by_y_index.splice(this._panel_ids_by_y_index.indexOf(id), 1);\n this.applyPanelYIndexesToPanelLayouts();\n\n // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space\n if (this._initialized) {\n this.positionPanels();\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n this.setDimensions(this.layout.width, this._total_height);\n }\n\n this.emit('panel_removed', id);\n\n return this;\n }\n\n /**\n * Refresh (or fetch) a plot's data from sources, regardless of whether position or state has changed\n * @public\n * @returns {Promise}\n */\n refresh() {\n return this.applyState();\n }\n\n /**\n * A user-defined callback function that can receive (and potentially act on) new plot data.\n * @callback externalDataCallback\n * @param {Object} new_data The body resulting from a data request. This represents the same information that would be passed to\n * a data layer making an equivalent request.\n * @param {Object} plot A reference to the plot object. This can be useful for listeners that react to the\n * structure of the data, instead of just displaying something.\n */\n\n /**\n * A user-defined callback function that can respond to errors received during a previous operation\n * @callback externalErrorCallback\n * @param err A representation of the error that occurred\n */\n\n /**\n * Allow newly fetched data to be made available outside the LocusZoom plot. For example, a callback could be\n * registered to draw an HTML table of top GWAS hits, and update that table whenever the plot region changes.\n *\n * This is a convenience method for external hooks. It registers an event listener and returns parsed data,\n * using the same fields syntax and underlying methods as data layers.\n *\n * @public\n * @listens event:data_rendered\n * @listens event:data_from_layer\n * @param {Object} [opts] Options\n * @param {String} [opts.from_layer=null] The ID string (`panel_id.layer_id`) of a specific data layer to be watched.\n * @param {Object} [opts.namespace] An object specifying where to find external data. See data layer documentation for details.\n * @param {Object} [opts.data_operations] An array of data operations. If more than one source of data is requested,\n * this is usually required in order to specify dependency order and join operations. See data layer documentation for details.\n * @param {externalErrorCallback} [opts.onerror] User defined function that is automatically called if a problem\n * occurs during the data request or subsequent callback operations\n * @param {externalDataCallback} success_callback Used defined function that is automatically called any time that\n * new data is received by the plot. Receives two arguments: (data, plot).\n * @return {function} The newly created event listener, to allow for later cleanup/removal\n */\n subscribeToData(opts, success_callback) {\n const { from_layer, namespace, data_operations, onerror } = opts;\n\n // Register an event listener that is notified whenever new data has been rendered\n const error_callback = onerror || function (err) {\n console.error('An error occurred while acting on an external callback', err);\n };\n\n if (from_layer) {\n // Option 1: Subscribe to a data layer. Receive a copy of the exact data it receives; no need to duplicate NS or data operations code in two places.\n const base_prefix = `${this.getBaseId()}.`;\n // Allow users to provide either `plot.panel.layer`, or `panel.layer`. The latter usually leads to more reusable code.\n const layer_target = from_layer.startsWith(base_prefix) ? from_layer : `${base_prefix}${from_layer}`;\n\n // Ensure that a valid layer exists to watch\n let is_valid_layer = false;\n for (let p of Object.values(this.panels)) {\n is_valid_layer = Object.values(p.data_layers).some((d) => d.getBaseId() === layer_target);\n if (is_valid_layer) {\n break;\n }\n }\n if (!is_valid_layer) {\n throw new Error(`Could not subscribe to unknown data layer ${layer_target}`);\n }\n\n const listener = (eventData) => {\n if (eventData.data.layer !== layer_target) {\n // Same event name fires for many layers; only fire success cb for the one layer we want\n return;\n }\n try {\n success_callback(eventData.data.content, this);\n } catch (error) {\n error_callback(error);\n }\n };\n\n this.on('data_from_layer', listener);\n return listener;\n }\n\n // Second option: subscribe to an explicit list of fields and namespaces. This is useful if the same piece of\n // data has to be displayed in multiple ways, eg if we just want an annotation (which is normally visualized\n // in connection to some other visualization)\n if (!namespace) {\n throw new Error(\"subscribeToData must specify the desired data: either 'from_layer' or 'namespace' option\");\n }\n\n const [entities, dependencies] = this.lzd.config_to_sources(namespace, data_operations); // Does not pass reference to initiator- we don't want external subscribers with the power to mutate the whole plot.\n const listener = () => {\n try {\n // NOTE TO FUTURE SELF: since this event does something async and not tied to a returned promise, unit tests will behave strangely,\n // even though this method totally works. Don't spend another hour scratching your head; this is the line to blame.\n this.lzd.getData(this.state, entities, dependencies)\n .then((new_data) => success_callback(new_data, this))\n .catch(error_callback);\n } catch (error) {\n // In certain cases, errors are thrown before a promise can be generated, and LZ error display seems to rely on these errors bubbling up\n error_callback(error);\n }\n };\n this.on('data_rendered', listener);\n return listener;\n }\n\n /**\n * Update state values and trigger a pull for fresh data on all data sources for all data layers\n * @public\n * @param {Object} state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\n * @listens event:match_requested\n * @fires event:data_requested\n * @fires event:layout_changed\n * @fires event:data_rendered\n * @fires event:state_changed\n * @fires event:region_changed\n */\n applyState(state_changes) {\n state_changes = state_changes || {};\n if (typeof state_changes != 'object') {\n throw new Error(`applyState only accepts an object; ${typeof state_changes} given`);\n }\n\n // Track what parameters will be modified. For bounds checking, we must take some preset values into account.\n let mods = { chr: this.state.chr, start: this.state.start, end: this.state.end };\n for (let property in state_changes) {\n mods[property] = state_changes[property];\n }\n mods = _updateStatePosition(mods, this.layout);\n\n // Apply new state to the actual state\n for (let property in mods) {\n this.state[property] = mods[property];\n }\n\n // Generate requests for all panels given new state\n this.emit('data_requested');\n this._remap_promises = [];\n this.loading_data = true;\n for (let id in this.panels) {\n this._remap_promises.push(this.panels[id].reMap());\n }\n\n return Promise.all(this._remap_promises)\n .catch((error) => {\n console.error(error);\n this.curtain.show(error.message || error);\n this.loading_data = false;\n })\n .then(() => {\n // Update toolbar / widgets\n this.toolbar.update();\n\n // Apply panel-level state values\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.toolbar.update();\n // Apply data-layer-level state values\n panel._data_layer_ids_by_z_index.forEach((data_layer_id) => {\n panel.data_layers[data_layer_id].applyAllElementStatus();\n });\n });\n\n // Emit events\n this.emit('layout_changed');\n this.emit('data_rendered');\n this.emit('state_changed', state_changes);\n\n // An interesting quirk of region changing in LZ: the final region is not always the same as the requested region\n // (example: zoom out beyond max, or request non-integer position)\n // Echo the actual plot region as the final source of truth\n const { chr, start, end } = this.state;\n const position_changed = Object.keys(state_changes)\n .some((key) => ['chr', 'start', 'end'].includes(key));\n\n if (position_changed) {\n this.emit('region_changed', { chr, start, end });\n }\n\n this.loading_data = false;\n });\n }\n\n /**\n * Keep a record of event listeners that are defined outside of the LocusZoom boundary (and therefore would not\n * get cleaned up when the plot was removed from the DOM). For example, window resize or mouse events.\n * This allows safe cleanup of the plot on removal from the page. This method is useful for authors of LocusZoom plugins.\n * @param {Node} target The node on which the listener has been defined\n * @param {String} event_name\n * @param {function} listener The handle for the event listener to be cleaned up\n */\n trackExternalListener(target, event_name, listener) {\n if (!this._external_listeners.has(target)) {\n this._external_listeners.set(target, new Map());\n }\n const container = this._external_listeners.get(target);\n\n const tracker = container.get(event_name) || [];\n if (!tracker.includes(listener)) {\n tracker.push(listener);\n }\n container.set(event_name, tracker);\n }\n\n /**\n * Remove the plot from the page, and clean up any globally registered event listeners\n *\n * Internally, the plot retains references to some nodes via selectors; it may be useful to delete the plot\n * instance after calling this method\n */\n destroy() {\n for (let [target, registered_events] of this._external_listeners.entries()) {\n for (let [event_name, listeners] of registered_events) {\n for (let listener of listeners) {\n target.removeEventListener(event_name, listener);\n }\n }\n }\n\n // Clear the SVG, plus other HTML nodes (like toolbar) that live under the same parent\n const parent = this.svg.node().parentNode;\n if (!parent) {\n throw new Error('Plot has already been removed');\n }\n while (parent.lastElementChild) {\n parent.removeChild(parent.lastElementChild);\n }\n // Clear toolbar event listeners defined on the parent lz-container. As of 2020 this appears to be the\n // state of the art cross-browser DOM API for this task.\n // eslint-disable-next-line no-self-assign\n parent.outerHTML = parent.outerHTML;\n\n this._initialized = false;\n\n this.svg = null;\n this.panels = null;\n }\n\n /**\n * Plots can change how data is displayed by layout mutations. In rare cases, such as swapping from one source of LD to another,\n * these layout mutations won't be picked up instantly. This method notifies the plot to recalculate any cached properties,\n * like data fetching logic, that might depend on initial layout. It does not trigger a re-render by itself.\n * @public\n */\n mutateLayout() {\n Object.values(this.panels).forEach((panel) => {\n Object.values(panel.data_layers).forEach((layer) => layer.mutateLayout());\n });\n }\n\n /******* The private interface: methods only used by LocusZoom internals */\n /**\n * Track whether the target panel can respond to mouse interaction events\n * @private\n * @param {String} panel_id\n * @returns {boolean}\n */\n _canInteract(panel_id) {\n panel_id = panel_id || null;\n const { _interaction } = this;\n if (panel_id) {\n return ((typeof _interaction.panel_id == 'undefined' || _interaction.panel_id === panel_id) && !this.loading_data);\n } else {\n return !(_interaction.dragging || _interaction.zooming || this.loading_data);\n }\n }\n\n /**\n * Get an object with the x and y coordinates of the plot's origin in terms of the entire page\n * This returns a result with absolute position relative to the page, regardless of current scrolling\n * Necessary for positioning any HTML elements over the plot\n * @private\n * @returns {{x: Number, y: Number, width: Number, height: Number}}\n */\n _getPageOrigin() {\n const bounding_client_rect = this.svg.node().getBoundingClientRect();\n let x_offset = document.documentElement.scrollLeft || document.body.scrollLeft;\n let y_offset = document.documentElement.scrollTop || document.body.scrollTop;\n let container = this.svg.node();\n while (container.parentNode !== null) {\n // TODO: Recursively seeks offsets for highest non-static parent node. This can lead to incorrect\n // calculations of, for example, x coordinate relative to the page. Revisit this logic.\n container = container.parentNode;\n if (container !== document && d3.select(container).style('position') !== 'static') {\n x_offset = -1 * container.getBoundingClientRect().left;\n y_offset = -1 * container.getBoundingClientRect().top;\n break;\n }\n }\n return {\n x: x_offset + bounding_client_rect.left,\n y: y_offset + bounding_client_rect.top,\n width: bounding_client_rect.width,\n height: bounding_client_rect.height,\n };\n }\n\n /**\n * Get the top and left offset values for the plot's container element (the div that was populated)\n * @private\n * @returns {{top: number, left: number}}\n */\n getContainerOffset() {\n const offset = { top: 0, left: 0 };\n let container = this.container.offsetParent || null;\n while (container !== null) {\n offset.top += container.offsetTop;\n offset.left += container.offsetLeft;\n container = container.offsetParent || null;\n }\n return offset;\n }\n\n /**\n * Notify each child panel of the plot of changes in panel ordering/ arrangement\n * @private\n */\n applyPanelYIndexesToPanelLayouts () {\n this._panel_ids_by_y_index.forEach((pid, idx) => {\n this.panels[pid].layout.y_index = idx;\n });\n }\n\n /**\n * Get the qualified ID pathname for the plot\n * @private\n * @returns {String}\n */\n getBaseId () {\n return this.id;\n }\n\n /**\n * Resize the plot to fit the bounding container\n * @private\n * @returns {Plot}\n */\n rescaleSVG() {\n const clientRect = this.svg.node().getBoundingClientRect();\n this.setDimensions(clientRect.width, clientRect.height);\n return this;\n }\n\n /**\n * Prepare the plot for first use by performing parameter validation, setting up panels, and calculating dimensions\n * @private\n * @returns {Plot}\n */\n initializeLayout() {\n // Sanity check layout values\n if (isNaN(this.layout.width) || this.layout.width <= 0) {\n throw new Error('Plot layout parameter `width` must be a positive number');\n }\n\n // Backwards compatible check: there was previously a third option. Anything truthy should thus act as \"responsive_resize: true\"\n this.layout.responsive_resize = !!this.layout.responsive_resize;\n\n // Add panels\n this.layout.panels.forEach((panel_layout) => {\n this.addPanel(panel_layout);\n });\n return this;\n }\n\n /**\n * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly.\n *\n * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise,\n * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and\n * rendered in the correct relative positions.\n * @private\n * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height\n * @returns {Plot}\n * @fires event:layout_changed\n */\n setDimensions(width, height) {\n // If width and height arguments were passed, then adjust plot dimensions to fit all panels\n // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions.\n if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) {\n // Resize operations may ask for a different amount of space than that used by panels.\n const height_scaling_factor = height / this._total_height;\n\n this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n // Override discrete values if resizing responsively\n if (this.layout.responsive_resize) {\n // All resize modes will affect width\n if (this.svg) {\n this.layout.width = Math.max(this.svg.node().parentNode.getBoundingClientRect().width, this.layout.min_width);\n }\n }\n // Resize/reposition panels to fit, update proportional origins if necessary\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_width = this.layout.width;\n // In this block, we are passing explicit dimensions that might require rescaling all panels at once\n const panel_height = panel.layout.height * height_scaling_factor;\n panel.setDimensions(panel_width, panel_height);\n panel.setOrigin(0, y_offset);\n y_offset += panel_height;\n panel.toolbar.update();\n });\n }\n\n // Set the plot height to the sum of all panels (using the \"real\" height values accounting for panel.min_height)\n const final_height = this._total_height;\n\n // Apply layout width and height as discrete values or viewbox values\n if (this.svg !== null) {\n // The viewBox must always be specified in order for \"save as image\" button to work\n this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', final_height);\n }\n\n // If the plot has been initialized then trigger some necessary render functions\n if (this._initialized) {\n this._panel_boundaries.position();\n this.toolbar.update();\n this.curtain.update();\n this.loader.update();\n }\n\n return this.emit('layout_changed');\n }\n\n /**\n * Automatically position panels based on panel positioning rules and values.\n * Keep panels from overlapping vertically by adjusting origins, and keep the sum of proportional heights at 1.\n *\n * LocusZoom panels can only be stacked vertically (not horizontally)\n * @private\n */\n positionPanels() {\n // We want to enforce that all x-linked panels have consistent horizontal margins\n // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters)\n // NOTE: This assumes panels have consistent widths already. That should probably be enforced too!\n const x_linked_margins = { left: 0, right: 0 };\n\n // Proportional heights for newly added panels default to null unless explicitly set, so determine appropriate\n // proportional heights for all panels with a null value from discretely set dimensions.\n // Likewise handle default nulls for proportional widths, but instead just force a value of 1 (full width)\n for (let panel of Object.values(this.panels)) {\n if (panel.layout.interaction.x_linked) {\n x_linked_margins.left = Math.max(x_linked_margins.left, panel.layout.margin.left);\n x_linked_margins.right = Math.max(x_linked_margins.right, panel.layout.margin.right);\n }\n }\n\n // Update origins on all panels without changing plot-level dimensions yet\n // Also apply x-linked margins to x-linked panels, updating widths as needed\n let y_offset = 0;\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n const panel_layout = panel.layout;\n panel.setOrigin(0, y_offset);\n y_offset += this.panels[panel_id].layout.height;\n if (panel_layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - panel_layout.margin.left, 0)\n + Math.max(x_linked_margins.right - panel_layout.margin.right, 0);\n panel_layout.width += delta;\n panel_layout.margin.left = x_linked_margins.left;\n panel_layout.margin.right = x_linked_margins.right;\n panel_layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n\n // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel,\n // also must update the plot dimensions)\n this.setDimensions();\n\n // Set dimensions on all panels using newly set plot-level dimensions and panel-level proportional dimensions\n this._panel_ids_by_y_index.forEach((panel_id) => {\n const panel = this.panels[panel_id];\n panel.setDimensions(\n this.layout.width,\n panel.layout.height,\n );\n });\n\n return this;\n }\n\n /**\n * Prepare the first rendering of the plot. This includes initializing the individual panels, but also creates shared\n * elements such as mouse events, panel guides/boundaries, and loader/curtain.\n * @private\n * @returns {Plot}\n */\n initialize() {\n // Ensure proper responsive class is present on the containing node if called for\n if (this.layout.responsive_resize) {\n d3.select(this.container).classed('lz-container-responsive', true);\n\n // If this is a responsive layout then set a namespaced/unique onresize event listener on the window\n const resize_listener = () => window.requestAnimationFrame(() => this.rescaleSVG());\n\n window.addEventListener('resize', resize_listener);\n this.trackExternalListener(window, 'resize', resize_listener);\n\n // Many libraries collapse/hide tab widgets using display:none, which doesn't trigger the resize listener\n // High threshold: Don't fire listeners on every 1px change, but allow this to work if the plot position is a bit cockeyed\n if (typeof IntersectionObserver !== 'undefined') { // don't do this in old browsers\n const options = { root: document.documentElement, threshold: 0.9 };\n const observer = new IntersectionObserver((entries, observer) => {\n if (entries.some((entry) => entry.intersectionRatio > 0)) {\n this.rescaleSVG();\n }\n }, options);\n // IntersectionObservers will be cleaned up when DOM node removed; no need to track them for manual cleanup\n observer.observe(this.container);\n }\n\n // Forcing one additional setDimensions() call after the page is loaded clears up\n // any disagreements between the initial layout and the loaded responsive container's size\n const load_listener = () => this.setDimensions();\n window.addEventListener('load', load_listener);\n this.trackExternalListener(window, 'load', load_listener);\n }\n\n // Create an element/layer for containing mouse guides\n if (this.layout.mouse_guide) {\n const mouse_guide_svg = this.svg.append('g')\n .attr('class', 'lz-mouse_guide')\n .attr('id', `${this.id}.mouse_guide`);\n const mouse_guide_vertical_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-vertical')\n .attr('x', -1);\n const mouse_guide_horizontal_svg = mouse_guide_svg.append('rect')\n .attr('class', 'lz-mouse_guide-horizontal')\n .attr('y', -1);\n this._mouse_guide = {\n svg: mouse_guide_svg,\n vertical: mouse_guide_vertical_svg,\n horizontal: mouse_guide_horizontal_svg,\n };\n }\n\n // Add curtain and loader prototpyes to the plot\n this.curtain = generateCurtain.call(this);\n this.loader = generateLoader.call(this);\n\n // Create the panel_boundaries object with show/position/hide methods\n this._panel_boundaries = {\n parent: this,\n hide_timeout: null,\n showing: false,\n dragging: false,\n selectors: [],\n corner_selector: null,\n show: function() {\n // Generate panel boundaries\n if (!this.showing && !this.parent.curtain.showing) {\n this.showing = true;\n // Loop through all panels to create a horizontal boundary for each\n this.parent._panel_ids_by_y_index.forEach((panel_id, panel_idx) => {\n const selector = d3.select(this.parent.svg.node().parentNode).insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-boundary')\n .attr('title', 'Resize panel');\n selector.append('span');\n const panel_resize_drag = d3.drag();\n panel_resize_drag.on('start', () => {\n this.dragging = true;\n });\n panel_resize_drag.on('end', () => {\n this.dragging = false;\n });\n panel_resize_drag.on('drag', () => {\n // First set the dimensions on the panel we're resizing\n const this_panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const original_panel_height = this_panel.layout.height;\n this_panel.setDimensions(this.parent.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n // Next loop through all panels.\n // Update proportional dimensions for all panels including the one we've resized using discrete heights.\n // Reposition panels with a greater y-index than this panel to their appropriate new origin.\n this.parent._panel_ids_by_y_index.forEach((loop_panel_id, loop_panel_idx) => {\n const loop_panel = this.parent.panels[this.parent._panel_ids_by_y_index[loop_panel_idx]];\n if (loop_panel_idx > panel_idx) {\n loop_panel.setOrigin(loop_panel.layout.origin.x, loop_panel.layout.origin.y + panel_height_change);\n loop_panel.toolbar.position();\n }\n });\n // Reset dimensions on the entire plot and reposition panel boundaries\n this.parent.positionPanels();\n this.position();\n });\n selector.call(panel_resize_drag);\n this.parent._panel_boundaries.selectors.push(selector);\n });\n // Create a corner boundary / resize element on the bottom-most panel that resizes the entire plot\n const corner_selector = d3.select(this.parent.svg.node().parentNode)\n .insert('div', '.lz-data_layer-tooltip')\n .attr('class', 'lz-panel-corner-boundary')\n .attr('title', 'Resize plot');\n\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-outer');\n corner_selector\n .append('span')\n .attr('class', 'lz-panel-corner-boundary-inner');\n\n const corner_drag = d3.drag();\n corner_drag.on('start', () => {\n this.dragging = true;\n });\n corner_drag.on('end', () => {\n this.dragging = false;\n });\n corner_drag.on('drag', () => {\n this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent._total_height + d3.event.dy);\n });\n corner_selector.call(corner_drag);\n this.parent._panel_boundaries.corner_selector = corner_selector;\n }\n return this.position();\n },\n position: function() {\n if (!this.showing) {\n return this;\n }\n // Position panel boundaries\n const plot_page_origin = this.parent._getPageOrigin();\n this.selectors.forEach((selector, panel_idx) => {\n const panel = this.parent.panels[this.parent._panel_ids_by_y_index[panel_idx]];\n const panel_page_origin = panel._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + panel.layout.height - 12;\n const width = this.parent.layout.width - 1;\n selector\n .style('top', `${top}px`)\n .style('left', `${left}px`)\n .style('width', `${width}px`);\n selector.select('span')\n .style('width', `${width}px`);\n });\n // Position corner selector\n const corner_padding = 10;\n const corner_size = 16;\n this.corner_selector\n .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`)\n .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`);\n return this;\n },\n hide: function() {\n if (!this.showing) {\n return this;\n }\n this.showing = false;\n // Remove panel boundaries\n this.selectors.forEach((selector) => {\n selector.remove();\n });\n this.selectors = [];\n // Remove corner boundary\n this.corner_selector.remove();\n this.corner_selector = null;\n return this;\n },\n };\n\n // Show panel boundaries stipulated by the layout (basic toggle, only show on mouse over plot)\n if (this.layout.panel_boundaries) {\n d3.select(this.svg.node().parentNode)\n .on(`mouseover.${this.id}.panel_boundaries`, () => {\n clearTimeout(this._panel_boundaries.hide_timeout);\n this._panel_boundaries.show();\n })\n .on(`mouseout.${this.id}.panel_boundaries`, () => {\n this._panel_boundaries.hide_timeout = setTimeout(() => {\n this._panel_boundaries.hide();\n }, 300);\n });\n }\n\n // Create the toolbar object and immediately show it\n this.toolbar = new Toolbar(this).show();\n\n // Initialize all panels\n for (let id in this.panels) {\n this.panels[id].initialize();\n }\n\n // Define plot-level mouse events\n const namespace = `.${this.id}`;\n if (this.layout.mouse_guide) {\n const mouseout_mouse_guide = () => {\n this._mouse_guide.vertical.attr('x', -1);\n this._mouse_guide.horizontal.attr('y', -1);\n };\n const mousemove_mouse_guide = () => {\n const coords = d3.mouse(this.svg.node());\n this._mouse_guide.vertical.attr('x', coords[0]);\n this._mouse_guide.horizontal.attr('y', coords[1]);\n };\n this.svg\n .on(`mouseout${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`touchleave${namespace}-mouse_guide`, mouseout_mouse_guide)\n .on(`mousemove${namespace}-mouse_guide`, mousemove_mouse_guide);\n }\n const mouseup = () => {\n this.stopDrag();\n };\n const mousemove = () => {\n const { _interaction } = this;\n if (_interaction.dragging) {\n const coords = d3.mouse(this.svg.node());\n if (d3.event) {\n d3.event.preventDefault();\n }\n _interaction.dragging.dragged_x = coords[0] - _interaction.dragging.start_x;\n _interaction.dragging.dragged_y = coords[1] - _interaction.dragging.start_y;\n this.panels[_interaction.panel_id].render();\n _interaction.linked_panel_ids.forEach((panel_id) => {\n this.panels[panel_id].render();\n });\n }\n };\n this.svg\n .on(`mouseup${namespace}`, mouseup)\n .on(`touchend${namespace}`, mouseup)\n .on(`mousemove${namespace}`, mousemove)\n .on(`touchmove${namespace}`, mousemove);\n\n // Add an extra namespaced mouseup handler to the containing body, if there is one\n // This helps to stop interaction events gracefully when dragging outside of the plot element\n const body_selector = d3.select('body');\n const body_node = body_selector.node();\n if (body_node) {\n body_node.addEventListener('mouseup', mouseup);\n body_node.addEventListener('touchend', mouseup);\n\n this.trackExternalListener(body_node, 'mouseup', mouseup);\n this.trackExternalListener(body_node, 'touchend', mouseup);\n }\n\n this.on('match_requested', (eventData) => {\n // Layers can broadcast that a specific point has been selected, and the plot will tell every other layer\n // to look for that value. Whenever a point is de-selected, it clears the match.\n const data = eventData.data;\n const to_send = (data.active ? data.value : null);\n const emitted_by = eventData.target.id;\n // When a match is initiated, hide all tooltips from other panels (prevents zombie tooltips from reopening)\n // TODO: This is a bit hacky. Right now, selection and matching are tightly coupled, and hence tooltips\n // reappear somewhat aggressively. A better solution depends on designing alternative behavior, and\n // applying tooltips post (instead of pre) render.\n Object.values(this.panels).forEach((panel) => {\n if (panel.id !== emitted_by) {\n Object.values(panel.data_layers).forEach((layer) => layer.destroyAllTooltips(false));\n }\n });\n\n this.applyState({ lz_match_value: to_send });\n });\n\n this._initialized = true;\n\n // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip\n // positioning. TODO: make this additional call unnecessary.\n const client_rect = this.svg.node().getBoundingClientRect();\n const width = client_rect.width ? client_rect.width : this.layout.width;\n const height = client_rect.height ? client_rect.height : this._total_height;\n this.setDimensions(width, height);\n\n return this;\n }\n\n /**\n * Register interactions along the specified axis, provided that the target panel allows interaction.\n * @private\n * @param {Panel} panel\n * @param {('background'|'x_tick'|'y1_tick'|'y2_tick')} method The direction (axis) along which dragging is being performed.\n * @returns {Plot}\n */\n startDrag(panel, method) {\n panel = panel || null;\n method = method || null;\n\n let axis = null;\n switch (method) {\n case 'background':\n case 'x_tick':\n axis = 'x';\n break;\n case 'y1_tick':\n axis = 'y1';\n break;\n case 'y2_tick':\n axis = 'y2';\n break;\n }\n\n if (!(panel instanceof Panel) || !axis || !this._canInteract()) {\n return this.stopDrag();\n }\n\n const coords = d3.mouse(this.svg.node());\n this._interaction = {\n panel_id: panel.id,\n linked_panel_ids: panel.getLinkedPanelIds(axis),\n dragging: {\n method: method,\n start_x: coords[0],\n start_y: coords[1],\n dragged_x: 0,\n dragged_y: 0,\n axis: axis,\n },\n };\n\n this.svg.style('cursor', 'all-scroll');\n\n return this;\n }\n\n /**\n * Process drag interactions across the target panel and synchronize plot state across other panels in sync;\n * clear the event when complete\n * @private\n * @returns {Plot}\n */\n stopDrag() {\n const { _interaction } = this;\n if (!_interaction.dragging) {\n return this;\n }\n\n if (typeof this.panels[_interaction.panel_id] != 'object') {\n this._interaction = {};\n return this;\n }\n const panel = this.panels[_interaction.panel_id];\n\n // Helper function to find the appropriate axis layouts on child data layers\n // Once found, apply the extent as floor/ceiling and remove all other directives\n // This forces all associated axes to conform to the extent generated by a drag action\n const overrideAxisLayout = (axis, axis_number, extent) => {\n panel._data_layer_ids_by_z_index.forEach((id) => {\n const axis_layout = panel.data_layers[id].layout[`${axis}_axis`];\n if (axis_layout.axis === axis_number) {\n axis_layout.floor = extent[0];\n axis_layout.ceiling = extent[1];\n delete axis_layout.lower_buffer;\n delete axis_layout.upper_buffer;\n delete axis_layout.min_extent;\n delete axis_layout.ticks;\n }\n });\n };\n\n switch (_interaction.dragging.method) {\n case 'background':\n case 'x_tick':\n if (_interaction.dragging.dragged_x !== 0) {\n overrideAxisLayout('x', 1, panel.x_extent);\n this.applyState({ start: panel.x_extent[0], end: panel.x_extent[1] });\n }\n break;\n case 'y1_tick':\n case 'y2_tick':\n if (_interaction.dragging.dragged_y !== 0) {\n const y_axis_number = parseInt(_interaction.dragging.method[1]);\n overrideAxisLayout('y', y_axis_number, panel[`y${y_axis_number}_extent`]);\n }\n break;\n }\n\n this._interaction = {};\n this.svg.style('cursor', null);\n\n return this;\n\n }\n\n get _total_height() {\n // The plot height is a calculated property, derived from the sum of its panel layout objects\n return this.layout.panels.reduce((acc, item) => item.height + acc, 0);\n }\n}\n\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * \"Match\" test functions used to compare two values for filtering (what to render) and matching\n * (comparison and finding related points across data layers)\n *\n * ### How do matching and filtering work?\n * See the Interactivity Tutorial for details.\n *\n * ## Adding a new function\n * LocusZoom allows users to write their own plugins, so that \"does this point match\" logic can incorporate\n * user-defined code. (via `LocusZoom.MatchFunctions.add('my_function', my_function);`)\n *\n * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\n *\n * Both filtering and matching depend on asking \"is this field interesting to me\", which is inherently a problem of\n * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that\n * function doesn't have to use either argument.\n *\n * @module LocusZoom_MatchFunctions\n */\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"match\" functions, used by filtering and matching behavior.\n * @alias module:LocusZoom~MatchFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\n// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another\n// module, just define and register them here.\n\n/**\n * Check if two values are (strictly) equal\n * @function\n * @name '='\n * @param item_value\n * @param target_value\n */\nregistry.add('=', (item_value, target_value) => item_value === target_value);\n\n/**\n * Check if two values are not equal. This allows weak comparisons (eg undefined/null), so it can also be used to test for the absence of a value\n * @function\n * @name '!='\n * @param item_value\n * @param target_value\n */\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n\n/**\n * Less-than comparison\n * @function\n * @name '<'\n * @param item_value\n * @param target_value\n */\nregistry.add('<', (a, b) => a < b);\n\n/**\n * Less than or equals to comparison\n * @function\n * @name '<='\n * @param item_value\n * @param target_value\n */\nregistry.add('<=', (a, b) => a <= b);\n\n/**\n * Greater-than comparison\n * @function\n * @name '>'\n * @param item_value\n * @param target_value\n */\nregistry.add('>', (a, b) => a > b);\n\n/**\n * Greater than or equals to comparison\n * @function\n * @name '>='\n * @param item_value\n * @param target_value\n */\nregistry.add('>=', (a, b) => a >= b);\n\n/**\n * Modulo: tests for whether the remainder a % b is nonzero\n * @function\n * @name '%'\n * @param item_value\n * @param target_value\n */\nregistry.add('%', (a, b) => a % b);\n\n/**\n * Check whether the provided value (a) is in the string or array of values (b)\n *\n * This can be used to check if a field value is one of a set of predefined choices\n * Eg, `gene_type` is one of the allowed types of interest\n * @function\n * @name 'in'\n * @param item_value A scalar value\n * @param {String|Array} target_value A container that implements the `includes` method\n */\nregistry.add('in', (a, b) => b && b.includes(a));\n\n/**\n * Partial-match function. Can be used for free text search (\"find all gene names that contain the user-entered string 'TCF'\")\n * @function\n * @name 'match'\n * @param {String|Array} item_value A container (like a string) that implements the `includes` method\n * @param target_value A scalar value, like a string\n */\nregistry.add('match', (a, b) => a && a.includes(b)); // useful for text search: \"find all gene names that contain the user-entered value HLA\"\n\n\nexport default registry;\n","/**\n * Plugin registry of available functions that can be used in scalable layout directives.\n *\n * These \"scale functions\" are used during rendering to return output (eg color) based on input value\n *\n * @module LocusZoom_ScaleFunctions\n * @see {@link module:LocusZoom_DataLayers~ScalableParameter} for details on how scale functions are used by datalayers\n */\n\nimport * as d3 from 'd3';\n\n/**\n * Basic conditional function to evaluate the value of the input field and return based on equality.\n * @alias module:LocusZoom_ScaleFunctions~if\n * @param {Object} parameters\n * @param {*} parameters.field_value The value against which to test the input value.\n * @param {*} parameters.then The value to return if the input value matches the field value\n * @param {*} parameters.else The value to return if the input value does not match the field value. Optional. If not\n * defined this scale function will return null (or value of null_value parameter, if defined) when input value fails\n * to match field_value.\n * @param {*} value value\n */\nconst if_value = (parameters, value) => {\n if (typeof value == 'undefined' || parameters.field_value !== value) {\n if (typeof parameters.else != 'undefined') {\n return parameters.else;\n } else {\n return null;\n }\n } else {\n return parameters.then;\n }\n};\n\n/**\n * Function to sort numerical values into bins based on numerical break points. Will only operate on numbers and\n * return null (or value of null_value parameter, if defined) if provided a non-numeric input value. Parameters:\n * @function numerical_bin\n * @param {Object} parameters\n * @param {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter. If the input value is greater than or equal to break n and less than\n * or equal to break n+1 (or break n+1 doesn't exist) then returned value is the nth entry in the values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against break points. Must be of\n * equal length to breaks parameter. Each entry n represents the value to return if the input value is greater than\n * or equal to break n and less than or equal to break n+1 (or break n+1 doesn't exist).\n * @param {*} parameters.null_value\n * @param {*} value value\n * @returns {*}\n */\nconst numerical_bin = (parameters, value) => {\n const breaks = parameters.breaks || [];\n const values = parameters.values || [];\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return (parameters.null_value ? parameters.null_value : null);\n }\n const threshold = breaks.reduce(function (prev, curr) {\n if (+value < prev || (+value >= prev && +value < curr)) {\n return prev;\n } else {\n return curr;\n }\n });\n return values[breaks.indexOf(threshold)];\n};\n\n/**\n * Function to sort values of any type into bins based on direct equality testing with a list of categories.\n * Will return null if provided an input value that does not match to a listed category.\n * @function categorical_bin\n * @param {Object} parameters\n * @param {Array} parameters.categories Array of values against which to evaluate the input value. Must be of equal\n * length to values parameter. If the input value is equal to category n then returned value is the nth entry in the\n * values parameter.\n * @param {Array} parameters.values Array of values to return given evaluations against categories. Must be of equal\n * length to categories parameter. Each entry n represents the value to return if the input value is equal to the nth\n * value in the categories parameter.\n * @param {*} parameters.null_value Value to return if the input value fails to match to any categories. Optional.\n */\nconst categorical_bin = (parameters, value) => {\n if (typeof value == 'undefined' || !parameters.categories.includes(value)) {\n return (parameters.null_value ? parameters.null_value : null);\n } else {\n return parameters.values[parameters.categories.indexOf(value)];\n }\n};\n\n/**\n * Cycle through a set of options, so that the each element in a set of data receives a value different than the\n * element before it. For example: \"use this palette of 10 colors to visually distinguish 100 adjacent items\"\n * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color\n * choices if the user pans to a region with a different number/order of items. (the same item is assigned a different color)\n *\n * See also: stable_choice.\n * @function ordinal_cycle\n * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n const options = parameters.values;\n return options[index % options.length];\n};\n\n/**\n * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every\n * time given the same value, regardless of ordering or what other data is in the region\n *\n * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values\n * the same color due to hash collisions.\n *\n * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache.\n * This function is therefore slightly less amenable to layout mutations like \"changing the options after scaling\n * function is used\", but this is not expected to be a common use case.\n *\n * CAVEAT: Some datasets do not return true datum ids, but instead append synthetic ID fields (\"item 1, item2\"...)\n * just to appease D3. This hash function only works if there is a meaningful, stable identifier in the data,\n * like a category or gene name.\n *\n * @function stable_choice\n *\n * @param parameters\n * @param {Array} [parameters.values] A list of options to choose from\n * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used\n * for unit testing, because stable choice is intended for datasets with a relatively limited number of\n * discrete categories.\n * @param value\n * @param index\n */\nlet stable_choice = (parameters, value, index) => {\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association - point color - directive 1\") rather than globally (\"all properties/panels\")\n const cache = parameters._cache = parameters._cache || new Map();\n const max_cache_size = parameters.max_cache_size || 500;\n\n if (cache.size >= max_cache_size) {\n // Prevent cache from growing out of control (eg as user moves between regions a lot)\n cache.clear();\n }\n if (cache.has(value)) {\n return cache.get(value);\n }\n\n // Simple JS hashcode implementation, from:\n // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript\n let hash = 0;\n value = String(value);\n for (let i = 0; i < value.length; i++) {\n let chr = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n // Convert signed 32 bit integer to be within the range of options allowed\n const options = parameters.values;\n const result = options[Math.abs(hash) % options.length];\n cache.set(value, result);\n return result;\n};\n\n/**\n * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points.\n * @function interpolate\n * @parameters {Object} parameters\n * @parameters {Number[]} parameters.breaks Array of numerical break points against which to evaluate the input value.\n * Must be of equal length to values parameter and contain at least two elements. Input value will be evaluated for\n * relative position between two break points n and n+1 and the returned value will be interpolated at a relative\n * position between values n and n+1.\n * @parameters {*[]} parameters.values Array of values to interpolate and return given evaluations against break\n * points. Must be of equal length to breaks parameter and contain at least two elements. Each entry n represents\n * the value to return if the input value matches the nth entry in breaks exactly. Note that this scale function\n * uses d3.interpolate to provide for effective interpolation of many different value types, including numbers,\n * colors, shapes, etc.\n * @parameters {*} parameters.null_value\n */\nconst interpolate = (parameters, value) => {\n var breaks = parameters.breaks || [];\n var values = parameters.values || [];\n var nullval = (parameters.null_value ? parameters.null_value : null);\n if (breaks.length < 2 || breaks.length !== values.length) {\n return nullval;\n }\n if (typeof value == 'undefined' || value === null || isNaN(+value)) {\n return nullval;\n }\n if (+value <= parameters.breaks[0]) {\n return values[0];\n } else if (+value >= parameters.breaks[parameters.breaks.length - 1]) {\n return values[breaks.length - 1];\n } else {\n var upper_idx = null;\n breaks.forEach(function (brk, idx) {\n if (!idx) {\n return;\n }\n if (breaks[idx - 1] <= +value && breaks[idx] >= +value) {\n upper_idx = idx;\n }\n });\n if (upper_idx === null) {\n return nullval;\n }\n const normalized_input = (+value - breaks[upper_idx - 1]) / (breaks[upper_idx] - breaks[upper_idx - 1]);\n if (!isFinite(normalized_input)) {\n return nullval;\n }\n return d3.interpolate(values[upper_idx - 1], values[upper_idx])(normalized_input);\n }\n};\n\n\n/**\n * Calculate the effect direction based on beta, or the combination of beta and standard error.\n * Typically used with phewas plots, to show point shape based on the beta and stderr_beta fields.\n *\n * @function effect_direction\n * @param parameters\n * @param parameters.'+' The value to return if the effect direction is positive\n * @param parameters.'-' The value to return if the effect direction is positive\n * @param parameters.beta_field The name of the field containing beta\n * @param parameters.stderr_beta_field The name of the field containing stderr_beta\n * @param {Object} input This function should receive the entire datum object, rather than one single field\n * @returns {null}\n */\nfunction effect_direction(parameters, input) {\n if (input === undefined) {\n return null;\n }\n\n const { beta_field, stderr_beta_field, '+': plus_result = null, '-': neg_result = null } = parameters;\n\n if (!beta_field || !stderr_beta_field) {\n throw new Error(`effect_direction must specify how to find required 'beta' and 'stderr_beta' fields`);\n }\n\n const beta_val = input[beta_field];\n const se_val = input[stderr_beta_field];\n\n if (beta_val !== undefined) {\n if (se_val !== undefined) {\n if ((beta_val - 1.96 * se_val) > 0) {\n return plus_result;\n } else if ((beta_val + 1.96 * se_val) < 0) {\n return neg_result || null;\n }\n } else {\n if (beta_val > 0) {\n return plus_result;\n } else if (beta_val < 0) {\n return neg_result;\n }\n }\n }\n // Note: The original PheWeb implementation allowed odds ratio in place of beta/se. LZ core is a bit more rigid\n // about expected data formats for layouts.\n return null;\n}\n\nexport { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle, effect_direction };\n","/**\n * Functions that control \"scalable\" layout directives: given a value (like a number) return another value\n * (like a color, size, or shape) that governs how something is displayed\n *\n * All scale functions have the call signature `(layout_parameters, input) => result|null`\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\n\n\nconst registry = new RegistryBase();\nfor (let [name, type] of Object.entries(scalable)) {\n registry.add(name, type);\n}\n\n// Alias for the \"if_value\" function (can't export reserved language keywords directly)\nregistry.add('if', scalable.if_value);\n\n\nexport default registry;\n","/**\n * Data layers represent instructions for how to render common types of information.\n * (GWAS scatter plot, nearby genes, straight lines and filled curves, etc)\n *\n * Each rendering type also provides helpful functionality such as filtering, matching, and interactive tooltip\n * display. Predefined layers can be extended or customized, with many configurable options.\n *\n * @module LocusZoom_DataLayers\n */\n\nimport * as d3 from 'd3';\n\nimport {STATUSES} from '../constants';\nimport Field from '../../data/field';\nimport {parseFields} from '../../helpers/display';\nimport {deepCopy, findFields, merge} from '../../helpers/layouts';\nimport MATCHERS from '../../registry/matchers';\nimport SCALABLE from '../../registry/scalable';\n\n\n/**\n * \"Scalable\" parameters indicate that a datum can be rendered in custom ways based on its value. (color, size, shape, etc)\n *\n * This means that if the value of this property is a scalar, it is used directly (`color: '#FF0000'`). But if the\n * value is an array of options, each will be evaluated in turn until the first non-null result is found. The syntax\n * below describes how each member of the array should specify the field and scale function to be used.\n * Often, the last item in the list is a string, providing a \"default\" value if all scale functions evaluate to null.\n *\n * @typedef {object[]|string} ScalableParameter\n * @property {string} [field] The name of the field to use in the scale function. If omitted, all fields for the given\n * datum element will be passed to the scale function.\n * @property {module:LocusZoom_ScaleFunctions} scale_function The name of a scale function that will be run on each individual datum\n * @property {object} parameters A set of parameters that configure the desired scale function (options vary by function)\n */\n\n\n/**\n * @typedef {Object} module:LocusZoom_DataLayers~behavior\n * @property {'set'|'unset'|'toggle'|'link'} action\n * @property {'highlighted'|'selected'|'faded'|'hidden'} status An element display status to set/unset/toggle\n * @property {boolean} exclusive Whether an element status should be exclusive (eg only allow one point to be selected at a time)\n * @property {string} href For links, the URL to visit when clicking\n * @property {string} target For links, the `target` attribute (eg, name of a window or tab in which to open this link)\n */\n\n\n/**\n * @typedef {object} FilterOption\n * @property {string} field The name of a field found within each datapoint datum\n * @property {module:LocusZoom_MatchFunctions} operator The name of a comparison function to use when deciding if the\n * field satisfies this filter\n * @property value The target value to compare to\n */\n\n/**\n * @typedef {object} DataOperation A synchronous function that modifies data returned from adapters, in order to clean up or reformat prior to plotting.\n * @property {module:LocusZoom_DataFunctions|'fetch'} type\n * @property {String[]} [from] For operations of type \"fetch\", this is required. By default, it will fill in any items provided in \"namespace\" (everything specified in namespace triggers an adapter/network request)\n * A namespace should be manually specified in this array when there are dependencies (one request depends on the content of what is returned from another namespace).\n * Eg, for ld to be fetched after association data, specify \"ld(assoc)\". Most LocusZoom examples fill in all items, in order to make the examples more clear.\n * @property {String} [name] The name of this operation. This only needs to be specified if a data layer performs several operations, and needs to refer to the result of a prior operation.\n * Eg, if the retrieved data is combined via several left joins in series: `name: \"assoc_plus_ld\"` -> feeds into `{name: \"final\", requires: [\"assoc_plus_ld\"]}`\n * @property {String[]} requires The names of each adapter required. This does not need to specify dependencies, just\n * the names of other namespaces (or data operations) whose results must be available before a join can be performed\n * @property {String[]} params Any user-defined parameters that should be passed to the particular join function\n * (see: {@link module:LocusZoom_DataFunctions} for details). For example, this could specify the left and right key fields for a join function, based on the expected field names used in this data layer: `params: ['assoc:position', 'ld:position2']`\n * Separate from this section, data functions will also receive a copy of \"plot.state\" automatically.\n */\n\n\n/**\n * @typedef {object} LegendItem\n * @property [shape] This is optional (e.g. a legend element could just be a textual label).\n * Supported values are the standard d3 3.x symbol types (i.e. \"circle\", \"cross\", \"diamond\", \"square\",\n * \"triangle-down\", and \"triangle-up\"), as well as \"rect\" for an arbitrary square/rectangle or \"line\" for a path.\n * A special \"ribbon\" option can be use to draw a series of explicit, numeric-only color stops (a row of colored squares, such as to indicate LD)\n * @property {string} color The point color (hexadecimal, rgb, etc)\n * @property {string} label The human-readable label of the legend item\n * @property {string} [class] The name of a CSS class used to style the point in the legend\n * @property {number} [size] The point area for each element (if the shape is a d3 symbol). Eg, for a 40 px area,\n * a circle would be ~7..14 px in diameter.\n * @property {number} [length] Length (in pixels) for the path rendered as the graphical portion of the legend element\n * if the value of the shape parameter is \"line\".\n * @property {number} [width] Width (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {number} [height] Height (in pixels) for the rect rendered as the graphical portion of the legend element if\n * the value of the shape parameter is \"rect\" or \"ribbon\".\n * @property {'vertical'|'horizontal'} [orientation='vertical'] For shape \"ribbon\", specifies whether to draw the ribbon vertically or horizontally.\n * @property {Array} [tick_labels] For shape \"ribbon\", specifies the tick labels that correspond to each colorstop value. Tick labels appear at all box edges: this array should have 1 more item than the number of colorstops.\n * @property {String[]} [color_stops] For shape \"ribbon\", specifies the colors of each box in the row of colored squares. There should be 1 fewer item in color_stops than the number of tick labels.\n * @property {object} style CSS styles object to be applied to the DOM element representing the graphical portion of\n * the legend element.\n */\n\n\n/**\n * A basic description of keys expected in all data layer layouts. Not intended to be directly used or modified by an end user.\n * @memberof module:LocusZoom_DataLayers~BaseDataLayer\n * @protected\n */\nconst default_layout = {\n id: '',\n type: '',\n tag: 'custom_data_type',\n namespace: {},\n data_operations: [],\n id_field: 'id',\n filters: null,\n match: {},\n x_axis: {},\n y_axis: {}, // Axis options vary based on data layer type\n legend: null,\n tooltip: {},\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n behaviors: {},\n};\n\n/**\n * A data layer is an abstract class representing a data set and its graphical representation within a panel\n * @public\n*/\nclass BaseDataLayer {\n /**\n * @param {string} [layout.id=''] An identifier string that must be unique across all layers within the same panel\n * @param {string} [layout.type=''] The type of data layer. This parameter is used in layouts to specify which class\n * (from the registry) is created; it is also used in CSS class names.\n * @param {string} [layout.tag='custom_data_type'] Tags have no functional purpose, but they can be used\n * as a semantic label for what is being displayed in this element. This makes it easy to write custom code like \"find every data\n * layer that shows association scatter plots, anywhere\": even if the IDs are different, the tag can be the same.\n * Most built-in data layers will contain a tag that describes, in human-readable terms, what kind of data is being shown.\n * (see: {@link LayoutRegistry.mutate_attrs})\n * @param {string} [layout.id_field] The datum field used for unique element IDs when addressing DOM elements, mouse\n * events, etc. This should be unique to the specified datum, and persistent across re-renders (because it is\n * used to identify where to draw tooltips, eg, if the plot is dragged or zoomed). If no single field uniquely\n * identifies all items, a template expression can be used to create an ID from multiple fields instead. (it is\n * your job to assure that all of the expected fields are present in every element)\n * @param {object} layout.namespace A set of key value pairs representing how to map the local usage of data (\"assoc\")\n * to the globally unique name for something defined in LocusZoom.DataSources.\n * Namespaces allow a single layout to be reused to plot many tracks of the same type: \"given some form of association data, plot it\".\n * These pairs take the form of { local_name: global_name }. (and all data layer layouts provide a default) In order to reuse\n * a layout with a new provider of data- like plotting two association studies stacked together-\n * only the namespace section of the layout needs to be overridden.\n * Eg, `LocusZoom.Layouts.get('data_layers', 'association_pvalues', { namespace: { assoc: 'assoc_study_2' }})`\n * @param {module:LocusZoom_DataLayers~DataOperation[]} layout.data_operations A set of data operations that will be performed on the data returned from the data adapters specified in `namespace`. (see: {@link module:LocusZoom_DataFunctions})\n * @param {module:LocusZoom_DataLayers~FilterOption[]} [layout.filters] If present, restricts the list of data elements to be displayed. Typically, filters\n * hide elements, but arrange the layer so as to leave the space those elements would have occupied. The exact\n * details vary from one layer to the next. See the Interactivity Tutorial for details.\n * @param {object} [layout.match] An object describing how to connect this data layer to other data layers in the\n * same plot. Specifies keys `send` and `receive` containing the names of fields with data to be matched;\n * `operator` specifies the name of a MatchFunction to use. If a datum matches the broadcast value, it will be\n * marked with the special field `lz_is_match=true`, which can be used in any scalable layout directive to control how the item is rendered.\n * @param {boolean} [layout.x_axis.decoupled=false] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {'state'|null} [layout.x_axis.extent] If provided, the region plot x-extent will be determined from\n * `plot.state` rather than from the range of the data. This is the most common way of setting x-extent,\n * as it is useful for drawing a set of panels to reflect a particular genomic region.\n * @param {number} [layout.x_axis.floor] The low end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.x_axis.ceiling] The high end of the x-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.x_axis.min_extent] The smallest possible range [min, max] of the x-axis. If the actual values lie outside the extent, the actual data takes precedence.\n * @param {number} [layout.x_axis.field] The datum field to look at when determining data extent along the x-axis.\n * @param {number} [layout.x_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.x_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {boolean} [layout.y_axis.decoupled=false] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {object} [layout.y_axis.axis=1] Which y axis to use for this data layer (left=1, right=2)\n * @param {number} [layout.y_axis.floor] The low end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {number} [layout.y_axis.ceiling] The high end of the y-extent, which overrides any actual data range, min_extent, or buffer options.\n * @param {Number[]} [layout.y_axis.min_extent] The smallest possible range [min, max] of the y-axis. Actual lower or higher data values will take precedence.\n * @param {number} [layout.y_axis.field] The datum field to look at when determining data extent along the y-axis.\n * @param {number} [layout.y_axis.lower_buffer] Amount to expand (pad) the lower end of an axis as a proportion of the extent of the data.\n * @param {number} [layout.y_axis.upper_buffer] Amount to expand (pad) the higher end of an axis as a proportion of the extent of the data.\n * @param {object} [layout.tooltip.show] Define when to show a tooltip in terms of interaction states, eg, `{ or: ['highlighted', 'selected'] }`\n * @param {object} [layout.tooltip.hide] Define when to hide a tooltip in terms of interaction states, eg, `{ and: ['unhighlighted', 'unselected'] }`\n * @param {boolean} [layout.tooltip.closable] Whether a tool tip should render a \"close\" button in the upper right corner.\n * @param {string} [layout.tooltip.html] HTML template to render inside the tool tip. The template syntax uses curly braces to allow simple expressions:\n * eg `{{sourcename:fieldname}} to insert a field value from the datum associated with\n * the tooltip/element. Conditional tags are supported using the format:\n * `{{#if sourcename:fieldname|transforms_can_be_used_too}}render text here{{#else}}Optional else branch{{/if}}`.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='horizontal'] Where to draw the tooltip relative to the datum.\n * Typically tooltip positions are centered around the midpoint of the data element, subject to overflow off the edge of the plot.\n * @param {object} [layout.behaviors] LocusZoom data layers support the binding of mouse events to one or more\n * layout-definable behaviors. Some examples of behaviors include highlighting an element on mouseover, or\n * linking to a dynamic URL on click, etc.\n * @param {module:LocusZoom_DataLayers~LegendItem[]} [layout.legend] Tick marks found in the panel legend\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onctrlshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onshiftclick]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseover]\n * @param {module:LocusZoom_DataLayers~behavior[]} [layout.behaviors.onmouseout]\n * @param {Panel|null} parent Where this layout is used\n */\n constructor(layout, parent) {\n /**\n * @private\n * @member {Boolean}\n */\n this._initialized = false;\n /**\n * @private\n * @member {Number}\n */\n this._layout_idx = null;\n\n /**\n * The unique identifier for this layer. Should be unique within this panel.\n * @public\n * @member {String}\n */\n this.id = null;\n\n /**\n * The fully qualified identifier for the data layer, prefixed by any parent or container elements.\n * @type {string}\n * @private\n */\n this._base_id = null;\n\n /**\n * @protected\n * @member {Panel}\n */\n this.parent = parent || null;\n /**\n * @private\n * @member {{group: d3.selection, container: d3.selection, clipRect: d3.selection}}\n */\n this.svg = {};\n\n /**\n * @protected\n * @member {Plot}\n */\n this.parent_plot = null;\n if (parent) {\n this.parent_plot = parent.parent;\n }\n\n /**\n * The current layout configuration for this data layer. This reflects any resizing or dynamically generated\n * config options produced during rendering. Direct layout mutations are a powerful way to dynamically\n * modify the plot in response to user interactions, but require a deep knowledge of LZ internals to use\n * effectively.\n * @public\n * @member {Object}\n */\n this.layout = merge(layout || {}, default_layout);\n if (this.layout.id) {\n this.id = this.layout.id;\n }\n\n /**\n * A user-provided function used to filter data for display. If provided, this will override any declarative\n * options in `layout.filters`\n * @private\n * @deprecated\n */\n this._filter_func = null;\n\n // Ensure any axes defined in the layout have an explicit axis number (default: 1)\n if (this.layout.x_axis !== {} && typeof this.layout.x_axis.axis !== 'number') {\n // TODO: Example of x2? if none remove\n this.layout.x_axis.axis = 1;\n }\n if (this.layout.y_axis !== {} && typeof this.layout.y_axis.axis !== 'number') {\n this.layout.y_axis.axis = 1;\n }\n\n /**\n * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state.\n * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is\n * loaded: it contains the \"defaults\", not just the result of a calculated value.\n * @ignore\n * @protected\n * @member {Object}\n */\n this._base_layout = deepCopy(this.layout);\n\n /**\n * @private\n * @member {Object}\n */\n this.state = {};\n /**\n * @private\n * @member {String}\n */\n this._state_id = null;\n\n /**\n * @private\n * @member {Object}\n * */\n this._layer_state = null;\n // Create a default state (and set any references to the parent as appropriate)\n this._setDefaultState();\n\n // Initialize parameters for storing data and tool tips\n /**\n * The data retrieved from a region request. This field is useful for debugging, but will be overridden on\n * re-render; do not modify it directly. The point annotation cache can be used to preserve markings\n * after re-render.\n * @protected\n * @member {Array}\n */\n this.data = [];\n if (this.layout.tooltip) {\n /**\n * @private\n * @member {Object}\n */\n this._tooltips = {};\n }\n\n // Initialize flags for tracking global statuses\n this._global_statuses = {\n 'highlighted': false,\n 'selected': false,\n 'faded': false,\n 'hidden': false,\n };\n\n // On first load, pre-parse the data specification once, so that it can be used for all other data retrieval\n this._data_contract = new Set(); // List of all fields requested by the layout\n this._entities = new Map();\n this._dependencies = [];\n this.mutateLayout(); // Parse data spec and any other changes that need to reflect the layout\n }\n\n /****** Public interface: methods for manipulating the layer from other parts of LZ */\n\n /**\n * @public\n */\n render() {\n throw new Error('Method must be implemented');\n }\n\n /**\n * Move a data layer forward relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveForward() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index + 1]) {\n layer_order[current_index] = layer_order[current_index + 1];\n layer_order[current_index + 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Move a data layer back relative to others by z-index\n * @public\n * @returns {BaseDataLayer}\n */\n moveBack() {\n const layer_order = this.parent._data_layer_ids_by_z_index;\n const current_index = this.layout.z_index;\n if (layer_order[current_index - 1]) {\n layer_order[current_index] = layer_order[current_index - 1];\n layer_order[current_index - 1] = this.id;\n this.parent.resortDataLayers();\n }\n return this;\n }\n\n /**\n * Set an \"annotation\": a piece of additional information about a point that is preserved across re-render,\n * or as the user pans and zooms near this region.\n *\n * Annotations can be referenced as a named pseudo-field in any filters and scalable parameters. (template support\n * may be added in the future)\n * Sample use case: user clicks a tooltip to \"label this specific point\". (or change any other display property)\n *\n * @public\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} key The name of the annotation to track\n * @param {*} value The value of the marked field\n */\n setElementAnnotation (element, key, value) {\n const id = this.getElementId(element);\n if (!this._layer_state.extra_fields[id]) {\n this._layer_state.extra_fields[id] = {};\n }\n this._layer_state.extra_fields[id][key] = value;\n return this;\n }\n\n /**\n * Select a filter function to be applied to the data. DEPRECATED: Please use the LocusZoom.MatchFunctions registry\n * and reference via declarative filters.\n * @param func\n * @deprecated\n */\n setFilter(func) {\n console.warn('The setFilter method is deprecated and will be removed in the future; please use the layout API with a custom filter function instead');\n this._filter_func = func;\n }\n\n /**\n * A list of operations that should be run when the layout is mutated\n * Typically, these are things done once when a layout is first specified, that would not automatically\n * update when the layout was changed.\n * @public\n */\n mutateLayout() {\n // Are we fetching data from external providers? If so, validate that those API calls would meet the expected contract.\n if (this.parent_plot) { // Don't run this method if instance isn't mounted to a plot, eg unit tests that don't require requester\n const { namespace, data_operations } = this.layout;\n this._data_contract = findFields(this.layout, Object.keys(namespace));\n const [entities, dependencies] = this.parent_plot.lzd.config_to_sources(namespace, data_operations, this);\n this._entities = entities;\n this._dependencies = dependencies;\n }\n }\n\n /********** Protected methods: useful in subclasses to manipulate data layer behaviors */\n /**\n * Implementation hook for fetching the min and max values of available data. Used to determine axis range, if no other\n * explicit axis settings override. Useful for data layers where the data extent depends on more than one field.\n * (eg confidence intervals in a forest plot)\n *\n * @protected\n * @param data\n * @param axis_config The configuration object for the specified axis.\n * @returns {Array} [min, max] without any padding applied\n */\n _getDataExtent (data, axis_config) {\n data = data || this.data;\n // By default this depends only on a single field.\n return d3.extent(data, (d) => {\n const f = new Field(axis_config.field);\n return +f.resolve(d);\n });\n }\n\n /**\n * Fetch the fully qualified ID to be associated with a specific visual element, based on the data to which that\n * element is bound. In general this element ID will be unique, allowing it to be addressed directly via selectors.\n *\n * The ID should also be stable across re-renders, so that tooltips and highlights may be reapplied to that\n * element as we switch regions or drag left/right. If the element is not unique along a single field (eg PheWAS data),\n * a unique ID can be generated via a template expression like `{{phewas:pheno}}-{{phewas:trait_label}}`\n * @protected\n * @param {Object} element The data associated with a particular element\n * @returns {String}\n */\n getElementId (element) {\n // Use a cached value if possible\n const id_key = Symbol.for('lzID');\n if (element[id_key]) {\n return element[id_key];\n }\n\n // Two ways to get element ID: field can specify an exact field name, or, we can parse a template expression\n const id_field = this.layout.id_field;\n let value = element[id_field];\n if (typeof value === 'undefined' && /{{[^{}]*}}/.test(id_field)) {\n // No field value was found directly, but if it looks like a template expression, next, try parsing that\n // WARNING: In this mode, it doesn't validate that all requested fields from the template are present. Only use this if you trust the data being given to the plot!\n value = parseFields(id_field, element, {}); // Not allowed to use annotations b/c IDs should be stable, and annos may be transient\n }\n if (value === null || value === undefined) {\n // Neither exact field nor template options produced an ID\n throw new Error('Unable to generate element ID');\n }\n const element_id = value.toString().replace(/\\W/g, '');\n\n // Cache ID value for future calls\n const key = (`${this.getBaseId()}-${element_id}`).replace(/([:.[\\],])/g, '_');\n element[id_key] = key;\n return key;\n }\n\n /**\n * Abstract method. It should be overridden by data layers that implement separate status\n * nodes, such as genes or intervals.\n * Fetch an ID that may bind a data element to a separate visual node for displaying status\n * Examples of this might be highlighting a gene with a surrounding box to show select/highlight statuses, or\n * a group of unrelated intervals (all markings grouped within a category).\n * @private\n * @param {String|Object} element\n * @returns {String|null}\n */\n getElementStatusNodeId (element) {\n return null;\n }\n\n /**\n * Returns a reference to the underlying data associated with a single visual element in the data layer, as\n * referenced by the unique identifier for the element\n *\n * @ignore\n * @protected\n * @param {String} id The unique identifier for the element, as defined by `getElementId`\n * @returns {Object|null} The data bound to that element\n */\n getElementById(id) {\n const selector = d3.select(`#${id.replace(/([:.[\\],])/g, '\\\\$1')}`); // escape special characters\n if (!selector.empty() && selector.data() && selector.data().length) {\n return selector.data()[0];\n } else {\n return null;\n }\n }\n\n /**\n * Basic method to apply arbitrary methods and properties to data elements.\n * This is called on all data immediately after being fetched. (requires reMap, not just re-render)\n *\n * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify\n * the parent plot. This is also used for system-derived fields like \"matching\" behavior\".\n *\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '=');\n const broadcast_value = this.parent_plot.state.lz_match_value;\n // Match functions are allowed to use transform syntax on field values, but not (yet) UI \"annotations\"\n const field_resolver = field_to_match ? new Field(field_to_match) : null;\n\n // Does the data from the API satisfy the list of fields expected by this layout?\n // Not every record will have every possible field (example: left joins like assoc + ld). The check is \"did\n // we see this field at least once in any record at all\".\n if (this.data.length && this._data_contract.size) {\n const fields_unseen = new Set(this._data_contract);\n for (let record of this.data) {\n Object.keys(record).forEach((field) => fields_unseen.delete(field));\n if (!fields_unseen.size) {\n // Once every requested field has been seen in at least one record, no need to look at more records\n break;\n }\n }\n if (fields_unseen.size) {\n // Current implementation is a soft warning, so that certain \"incremental enhancement\" features\n // (like rsIDs in tooltips) can fail gracefully if the API does not provide the requested info.\n // Template syntax like `{{#if fieldname}}` means that throwing an Error is not always the right answer for missing data.\n console.debug(`Data layer '${this.getBaseId()}' did not receive all expected fields from retrieved data. Missing fields are: ${[...fields_unseen]}\n Common reasons for this error include API payloads with missing fields, or data layer layouts that use two kinds of data and need join logic in \"data_operations\" \n If this field is optional, you can safely ignore this message. Examples include {{#if value}} tags or conditional color rules.\n`);\n }\n }\n\n this.data.forEach((item, i) => {\n // Basic toHTML() method - return the stringified value in the id_field, if defined.\n\n // When this layer receives data, mark whether points match (via a synthetic boolean field)\n // Any field-based layout directives (color, size, shape) can then be used to control display\n if (field_to_match && broadcast_value !== null && broadcast_value !== undefined) {\n item.lz_is_match = match_function(field_resolver.resolve(item), broadcast_value);\n }\n\n // Helper methods - return a reference to various plot levels. Useful for interactive tooltips.\n item.getDataLayer = () => this;\n item.getPanel = () => this.parent || null;\n item.getPlot = () => {\n // For unit testing etc, this layer may be created without a parent.\n const panel = this.parent;\n return panel ? panel.parent : null;\n };\n });\n this.applyCustomDataMethods();\n return this;\n }\n\n /**\n * Hook that allows custom datalayers to apply additional methods and properties to data elements as needed.\n * Most data layers will never need to use this.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyCustomDataMethods() {\n return this;\n }\n\n /**\n * Apply scaling functions to an element as needed, based on the layout rules governing display + the element's data\n * If the layout parameter is already a primitive type, simply return the value as given\n *\n * In the future this may be further expanded, so that scaling functions can operate similar to mappers\n * (item, index, array). Additional arguments would be added as the need arose.\n *\n * @private\n * @param {Array|Number|String|Object} option_layout Either a scalar (\"color is red\") or a configuration object\n * (\"rules for how to choose color based on item value\")\n * @param {*} element_data The value to be used with the filter. May be a primitive value, or a data object for a single item\n * @param {Number} data_index The array index for the data element\n * @returns {*} The transformed value\n */\n resolveScalableParameter (option_layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(option_layout)) {\n let idx = 0;\n while (ret === null && idx < option_layout.length) {\n ret = this.resolveScalableParameter(option_layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof option_layout) {\n case 'number':\n case 'string':\n ret = option_layout;\n break;\n case 'object':\n if (option_layout.scale_function) {\n const func = SCALABLE.get(option_layout.scale_function);\n if (option_layout.field) {\n const f = new Field(option_layout.field);\n let extra;\n try {\n extra = this.getElementAnnotation(element_data);\n } catch (e) {\n extra = null;\n }\n ret = func(option_layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(option_layout.parameters || {}, element_data, data_index);\n }\n }\n break;\n }\n }\n return ret;\n }\n\n /**\n * Generate dimension extent function based on layout parameters\n * @ignore\n * @protected\n * @param {('x'|'y')} dimension\n */\n getAxisExtent (dimension) {\n if (!['x', 'y'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n\n const axis_name = `${dimension}_axis`;\n const axis_layout = this.layout[axis_name];\n\n // If a floor AND a ceiling are explicitly defined then just return that extent and be done\n if (!isNaN(axis_layout.floor) && !isNaN(axis_layout.ceiling)) {\n return [+axis_layout.floor, +axis_layout.ceiling];\n }\n\n // If a field is defined for the axis and the data layer has data then generate the extent from the data set\n let data_extent = [];\n if (axis_layout.field && this.data) {\n if (!this.data.length) {\n // If data has been fetched (but no points in region), enforce the min_extent (with no buffers,\n // because we don't need padding around an empty screen)\n data_extent = axis_layout.min_extent || [];\n return data_extent;\n } else {\n data_extent = this._getDataExtent(this.data, axis_layout);\n\n // Apply upper/lower buffers, if applicable\n const original_extent_span = data_extent[1] - data_extent[0];\n if (!isNaN(axis_layout.lower_buffer)) {\n data_extent[0] -= original_extent_span * axis_layout.lower_buffer;\n }\n if (!isNaN(axis_layout.upper_buffer)) {\n data_extent[1] += original_extent_span * axis_layout.upper_buffer;\n }\n\n if (typeof axis_layout.min_extent == 'object') {\n // The data should span at least the range specified by min_extent, an array with [low, high]\n const range_min = axis_layout.min_extent[0];\n const range_max = axis_layout.min_extent[1];\n if (!isNaN(range_min) && !isNaN(range_max)) {\n data_extent[0] = Math.min(data_extent[0], range_min);\n }\n if (!isNaN(range_max)) {\n data_extent[1] = Math.max(data_extent[1], range_max);\n }\n }\n // If specified, floor and ceiling will override the actual data range\n return [\n isNaN(axis_layout.floor) ? data_extent[0] : axis_layout.floor,\n isNaN(axis_layout.ceiling) ? data_extent[1] : axis_layout.ceiling,\n ];\n }\n }\n\n // If this is for the x axis and no extent could be generated yet but state has a defined start and end\n // then default to using the state-defined region as the extent\n if (dimension === 'x' && !isNaN(this.state.start) && !isNaN(this.state.end)) {\n return [this.state.start, this.state.end];\n }\n\n // No conditions met for generating a valid extent, return an empty array\n return [];\n }\n\n /**\n * Allow this data layer to tell the panel what axis ticks it thinks it will require. The panel may choose whether\n * to use some, all, or none of these when rendering, either alone or in conjunction with other data layers.\n *\n * This method is a stub and should be overridden in data layers that need to specify custom behavior.\n *\n * @protected\n * @param {('x'|'y1'|'y2')} dimension\n * @param {Object} [config] Additional parameters for the panel to specify how it wants ticks to be drawn. The names\n * and meanings of these parameters may vary between different data layers.\n * @returns {Object[]}\n * An array of objects: each object must have an 'x' attribute to position the tick.\n * Other supported object keys:\n * * text: string to render for a given tick\n * * style: d3-compatible CSS style object\n * * transform: SVG transform attribute string\n * * color: string or LocusZoom scalable parameter object\n */\n getTicks (dimension, config) {\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error(`Invalid dimension identifier ${dimension}`);\n }\n return [];\n }\n\n /**\n * Determine the coordinates for where to point the tooltip at. Typically, this is the center of a datum element (eg,\n * the middle of a scatter plot point). Also provide an offset if the tooltip should not be at that center (most\n * elements are not single points, eg a scatter plot point has a radius and a gene is a rectangle).\n * The default implementation is quite naive: it places the tooltip at the origin for that layer. Individual layers\n * should override this method to position relative to the chosen data element or mouse event.\n * @protected\n * @param {Object} tooltip A tooltip object (including attribute tooltip.data)\n * @returns {Object} as {x_min, x_max, y_min, y_max} in px, representing bounding box of a rectangle around the data pt\n * Note that these pixels are in the SVG coordinate system\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n const y_extent = panel[`y${this.layout.y_axis.axis}_extent`];\n\n const x = panel.x_scale(panel.x_extent[0]);\n const y = y_scale(y_extent[0]);\n\n return { x_min: x, x_max: x, y_min: y, y_max: y };\n }\n\n /**\n * Draw a tooltip on the data layer pointed at the specified coordinates, in the specified orientation.\n * Tooltip will be drawn on the edge of the major axis, and centered along the minor axis- see diagram.\n * v\n * > o <\n * ^\n *\n * @protected\n * @param tooltip {Object} The object representing all data for the tooltip to be drawn\n * @param {'vertical'|'horizontal'|'top'|'bottom'|'left'|'right'} position Where to draw the tooltip relative to\n * the data\n * @param {Number} x_min The min x-coordinate for the bounding box of the data element\n * @param {Number} x_max The max x-coordinate for the bounding box of the data element\n * @param {Number} y_min The min y-coordinate for the bounding box of the data element\n * @param {Number} y_max The max y-coordinate for the bounding box of the data element\n */\n _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) {\n const panel_layout = this.parent.layout;\n const plot_layout = this.parent_plot.layout;\n const layer_layout = this.layout;\n\n // Tooltip position params: as defined in the default stylesheet, used in calculations\n const arrow_size = 7;\n const stroke_width = 1;\n const arrow_total = arrow_size + stroke_width; // Tooltip pos should account for how much space the arrow takes up\n\n const tooltip_padding = 6; // bbox size must account for any internal padding applied between data and border\n\n const page_origin = this._getPageOrigin();\n const tooltip_box = tooltip.selector.node().getBoundingClientRect();\n const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom);\n const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right);\n\n // Clip the edges of the datum to the available plot area\n x_min = Math.max(x_min, 0);\n x_max = Math.min(x_max, data_layer_width);\n y_min = Math.max(y_min, 0);\n y_max = Math.min(y_max, data_layer_height);\n\n const x_center = (x_min + x_max) / 2;\n const y_center = (y_min + y_max) / 2;\n // Default offsets are the far edge of the datum bounding box\n let x_offset = x_max - x_center;\n let y_offset = y_max - y_center;\n let placement = layer_layout.tooltip_positioning;\n\n // Coordinate system note: the tooltip is positioned relative to the plot/page; the arrow is positioned relative to\n // the tooltip boundaries\n let tooltip_top, tooltip_left, arrow_type, arrow_top, arrow_left;\n\n // The user can specify a generic orientation, and LocusZoom will autoselect whether to place the tooltip above or below\n if (placement === 'vertical') {\n // Auto-select whether to position above the item, or below\n x_offset = 0;\n if (tooltip_box.height + arrow_total > data_layer_height - (y_center + y_offset)) {\n placement = 'top';\n } else {\n placement = 'bottom';\n }\n } else if (placement === 'horizontal') {\n // Auto select whether to position to the left of the item, or to the right\n y_offset = 0;\n if (x_center <= plot_layout.width / 2) {\n placement = 'left';\n } else {\n placement = 'right';\n }\n }\n\n if (placement === 'top' || placement === 'bottom') {\n // Position horizontally centered above the point\n const offset_right = Math.max((tooltip_box.width / 2) - x_center, 0);\n const offset_left = Math.max((tooltip_box.width / 2) + x_center - data_layer_width, 0);\n tooltip_left = page_origin.x + x_center - (tooltip_box.width / 2) - offset_left + offset_right;\n arrow_left = page_origin.x + x_center - tooltip_left - arrow_size; // Arrow should be centered over the data\n // Position vertically above the point unless there's insufficient space, then go below\n if (placement === 'top') {\n tooltip_top = page_origin.y + y_center - (y_offset + tooltip_box.height + arrow_total);\n arrow_type = 'down';\n arrow_top = tooltip_box.height - stroke_width;\n } else {\n tooltip_top = page_origin.y + y_center + y_offset + arrow_total;\n arrow_type = 'up';\n arrow_top = 0 - arrow_total;\n }\n } else if (placement === 'left' || placement === 'right') {\n // Position tooltip horizontally on the left or the right depending on which side of the plot the point is on\n if (placement === 'left') {\n tooltip_left = page_origin.x + x_center + x_offset + arrow_total;\n arrow_type = 'left';\n arrow_left = -1 * (arrow_size + stroke_width);\n } else {\n tooltip_left = page_origin.x + x_center - tooltip_box.width - x_offset - arrow_total;\n arrow_type = 'right';\n arrow_left = tooltip_box.width - stroke_width;\n }\n // Position with arrow vertically centered along tooltip edge unless we're at the top or bottom of the plot\n if (y_center - (tooltip_box.height / 2) <= 0) { // Too close to the top, push it down\n tooltip_top = page_origin.y + y_center - (1.5 * arrow_size) - tooltip_padding;\n arrow_top = tooltip_padding;\n } else if (y_center + (tooltip_box.height / 2) >= data_layer_height) { // Too close to the bottom, pull it up\n tooltip_top = page_origin.y + y_center + arrow_size + tooltip_padding - tooltip_box.height;\n arrow_top = tooltip_box.height - (2 * arrow_size) - tooltip_padding;\n } else { // vertically centered\n tooltip_top = page_origin.y + y_center - (tooltip_box.height / 2);\n arrow_top = (tooltip_box.height / 2) - arrow_size;\n }\n } else {\n throw new Error('Unrecognized placement value');\n }\n\n // Position the div itself, relative to the layer origin\n tooltip.selector\n .style('left', `${tooltip_left}px`)\n .style('top', `${tooltip_top}px`);\n // Create / update position on arrow connecting tooltip to data\n if (!tooltip.arrow) {\n tooltip.arrow = tooltip.selector.append('div')\n .style('position', 'absolute');\n }\n tooltip.arrow\n .attr('class', `lz-data_layer-tooltip-arrow_${arrow_type}`)\n .style('left', `${arrow_left}px`)\n .style('top', `${arrow_top}px`);\n return this;\n }\n\n /**\n * Determine whether a given data element matches all predefined filter criteria, usually as specified in a layout directive.\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @private\n * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators. If the field is omitted, the entire datum object will be\n * passed to the filter, rather than a single scalar value. (this is only useful with custom `MatchFunctions` as operator)\n * @param {Object} item\n * @param {Number} index\n * @param {Array} array\n * @returns {Boolean} Whether the specified item is a match\n */\n filter(filter_rules, item, index, array) {\n let is_match = true;\n filter_rules.forEach((filter) => { // Try each filter on this item, in sequence\n const {field, operator, value: target} = filter;\n const test_func = MATCHERS.get(operator);\n\n // Return the field value or annotation. If no `field` is specified, the filter function will operate on\n // the entire data object. This behavior is only really useful with custom functions, because the\n // builtin ones expect to receive a scalar value\n const extra = this.getElementAnnotation(item);\n const field_value = field ? (new Field(field)).resolve(item, extra) : item;\n if (!test_func(field_value, target)) {\n is_match = false;\n }\n });\n return is_match;\n }\n\n /**\n * Get \"annotation\" metadata associated with a particular point.\n *\n * @protected\n * @param {String|Object} element The data object or ID string for the element\n * @param {String} [key] The name of the annotation to track. If omitted, returns all annotations for this element as an object.\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this._layer_state.extra_fields[id];\n return key ? (extra && extra[key]) : extra;\n }\n\n /****** Private methods: rarely overridden or modified by external usages */\n\n /**\n * Apply filtering options to determine the set of data to render\n *\n * This must be applied on rendering, not fetch, so that the axis limits reflect the true range of the dataset\n * Otherwise, two stacked panels (same dataset filtered in different ways) might not line up on the x-axis when\n * filters are applied.\n * @param data\n * @return {*}\n * @private\n */\n _applyFilters(data) {\n data = data || this.data;\n\n if (this._filter_func) {\n data = data.filter(this._filter_func);\n } else if (this.layout.filters) {\n data = data.filter(this.filter.bind(this, this.layout.filters));\n }\n return data;\n }\n\n /**\n * Define default state that should get tracked during the lifetime of this layer.\n *\n * In some special custom usages, it may be useful to completely reset a panel (eg \"click for\n * genome region\" links), plotting new data that invalidates any previously tracked state. This hook makes it\n * possible to reset without destroying the panel entirely. It is used by `Plot.clearPanelData`.\n * @private\n */\n _setDefaultState() {\n // Each datalayer tracks two kinds of status: flags for internal state (highlighted, selected, tooltip),\n // and \"extra fields\" (annotations like \"show a tooltip\" that are not determined by the server, but need to\n // persist across re-render)\n const _layer_state = { status_flags: {}, extra_fields: {} };\n const status_flags = _layer_state.status_flags;\n STATUSES.adjectives.forEach((status) => {\n status_flags[status] = status_flags[status] || new Set();\n });\n // Also initialize \"internal-only\" state fields (things that are tracked, but not set directly by external events)\n status_flags['has_tooltip'] = status_flags['has_tooltip'] || new Set();\n\n if (this.parent) {\n // If layer has a parent, store a reference in the overarching plot.state object\n this._state_id = `${this.parent.id}.${this.id}`;\n this.state = this.parent.state;\n this.state[this._state_id] = _layer_state;\n }\n this._layer_state = _layer_state;\n }\n\n /**\n * Get the fully qualified identifier for the data layer, prefixed by any parent or container elements\n *\n * @private\n * @returns {string} A dot-delimited string of the format ..\n */\n getBaseId () {\n if (this._base_id) {\n return this._base_id;\n }\n\n if (this.parent) {\n return `${this.parent_plot.id}.${this.parent.id}.${this.id}`;\n } else {\n return (this.id || '').toString();\n }\n }\n\n /**\n * Determine the pixel height of data-bound objects represented inside this data layer. (excluding elements such as axes)\n *\n * May be used by operations that resize the data layer to fit available data\n *\n * @private\n * @returns {number}\n */\n getAbsoluteDataHeight() {\n const dataBCR = this.svg.group.node().getBoundingClientRect();\n return dataBCR.height;\n }\n\n /**\n * Initialize a data layer\n * @private\n * @returns {BaseDataLayer}\n */\n initialize() {\n this._base_id = this.getBaseId();\n\n // Append a container group element to house the main data layer group element and the clip path\n const base_id = this.getBaseId();\n this.svg.container = this.parent.svg.group.append('g')\n .attr('class', 'lz-data_layer-container')\n .attr('id', `${base_id}.data_layer_container`);\n\n // Append clip path to the container element\n this.svg.clipRect = this.svg.container.append('clipPath')\n .attr('id', `${base_id}.clip`)\n .append('rect');\n\n // Append svg group for rendering all data layer elements, clipped by the clip path\n this.svg.group = this.svg.container.append('g')\n .attr('id', `${base_id}.data_layer`)\n .attr('clip-path', `url(#${base_id}.clip)`);\n\n return this;\n\n }\n\n /**\n * Generate a tool tip for a given element\n * @private\n * @param {String|Object} data Data for the element associated with the tooltip\n */\n createTooltip (data) {\n if (typeof this.layout.tooltip != 'object') {\n throw new Error(`DataLayer [${this.id}] layout does not define a tooltip`);\n }\n const id = this.getElementId(data);\n if (this._tooltips[id]) {\n this.positionTooltip(id);\n return;\n }\n this._tooltips[id] = {\n data: data,\n arrow: null,\n selector: d3.select(this.parent_plot.svg.node().parentNode).append('div')\n .attr('class', 'lz-data_layer-tooltip')\n .attr('id', `${id}-tooltip`),\n };\n this._layer_state.status_flags['has_tooltip'].add(id);\n this.updateTooltip(data);\n return this;\n }\n\n /**\n * Update a tool tip (generate its inner HTML)\n *\n * @private\n * @param {String|Object} d The element associated with the tooltip\n * @param {String} [id] An identifier to the tooltip\n */\n updateTooltip(d, id) {\n if (typeof id == 'undefined') {\n id = this.getElementId(d);\n }\n // Empty the tooltip of all HTML (including its arrow!)\n this._tooltips[id].selector.html('');\n this._tooltips[id].arrow = null;\n // Set the new HTML\n if (this.layout.tooltip.html) {\n this._tooltips[id].selector.html(parseFields(this.layout.tooltip.html, d, this.getElementAnnotation(d)));\n }\n // If the layout allows tool tips on this data layer to be closable then add the close button\n // and add padding to the tooltip to accommodate it\n if (this.layout.tooltip.closable) {\n this._tooltips[id].selector.insert('button', ':first-child')\n .attr('class', 'lz-tooltip-close-button')\n .attr('title', 'Close')\n .text('×')\n .on('click', () => {\n this.destroyTooltip(id);\n });\n }\n // Apply data directly to the tool tip for easier retrieval by custom UI elements inside the tool tip\n this._tooltips[id].selector.data([d]);\n // Reposition and draw a new arrow\n this.positionTooltip(id);\n return this;\n }\n\n /**\n * Destroy tool tip - remove the tool tip element from the DOM and delete the tool tip's record on the data layer\n *\n * @private\n * @param {String|Object} element_or_id The element (or id) associated with the tooltip\n * @param {boolean} [temporary=false] Whether this is temporary (not to be tracked in state). Differentiates\n * \"recreate tooltips on re-render\" (which is temporary) from \"user has closed this tooltip\" (permanent)\n * @returns {BaseDataLayer}\n */\n destroyTooltip(element_or_id, temporary) {\n let id;\n if (typeof element_or_id == 'string') {\n id = element_or_id;\n } else {\n id = this.getElementId(element_or_id);\n }\n if (this._tooltips[id]) {\n if (typeof this._tooltips[id].selector == 'object') {\n this._tooltips[id].selector.remove();\n }\n delete this._tooltips[id];\n }\n // When a tooltip is removed, also remove the reference from the state\n if (!temporary) {\n const tooltip_state = this._layer_state.status_flags['has_tooltip'];\n tooltip_state.delete(id);\n }\n return this;\n }\n\n /**\n * Loop through and destroy all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n destroyAllTooltips(temporary = true) {\n for (let id in this._tooltips) {\n this.destroyTooltip(id, temporary);\n }\n return this;\n }\n\n /**\n * Position and then redraw tool tip - naïve function to place a tool tip in the data layer. By default, positions wrt\n * the top-left corner of the data layer.\n *\n * Each layer type may have more specific logic. Consider overriding the provided hooks `_getTooltipPosition` or\n * `_drawTooltip` as appropriate\n *\n * @private\n * @param {String} id The identifier of the tooltip to position\n * @returns {BaseDataLayer}\n */\n positionTooltip(id) {\n if (typeof id != 'string') {\n throw new Error('Unable to position tooltip: id is not a string');\n }\n if (!this._tooltips[id]) {\n throw new Error('Unable to position tooltip: id does not point to a valid tooltip');\n }\n const tooltip = this._tooltips[id];\n const coords = this._getTooltipPosition(tooltip);\n\n if (!coords) {\n // Special cutout: normally, tooltips are positioned based on the datum element. Some, like lines/curves,\n // work better if based on a mouse event. Since not every redraw contains a mouse event, we can just skip\n // calculating position when no position information is available.\n return null;\n }\n this._drawTooltip(tooltip, this.layout.tooltip_positioning, coords.x_min, coords.x_max, coords.y_min, coords.y_max);\n }\n\n /**\n * Loop through and position all tool tips on this data layer\n *\n * @private\n * @returns {BaseDataLayer}\n */\n positionAllTooltips() {\n for (let id in this._tooltips) {\n this.positionTooltip(id);\n }\n return this;\n }\n\n /**\n * Show or hide a tool tip by ID depending on directives in the layout and state values relative to the ID\n *\n * @private\n * @param {String|Object} element The element associated with the tooltip\n * @param {boolean} first_time Because panels can re-render, the rules for showing a tooltip\n * depend on whether this is the first time a status change affecting display has been applied.\n * @returns {BaseDataLayer}\n */\n showOrHideTooltip(element, first_time) {\n const tooltip_layout = this.layout.tooltip;\n if (typeof tooltip_layout != 'object') {\n return this;\n }\n const id = this.getElementId(element);\n\n /**\n * Apply rules and decide whether to show or hide the tooltip\n * @param {Object} statuses All statuses that apply to an element\n * @param {String[]|object} directive A layout directive object\n * @param operator\n * @returns {null|bool}\n */\n const resolveStatus = (statuses, directive, operator) => {\n let status = null;\n if (typeof statuses != 'object' || statuses === null) {\n return null;\n }\n if (Array.isArray(directive)) {\n // This happens when the function is called on the inner part of the directive\n operator = operator || 'and';\n if (directive.length === 1) {\n status = statuses[directive[0]];\n } else {\n status = directive.reduce((previousValue, currentValue) => {\n if (operator === 'and') {\n return statuses[previousValue] && statuses[currentValue];\n } else if (operator === 'or') {\n return statuses[previousValue] || statuses[currentValue];\n }\n return null;\n });\n }\n } else if (typeof directive == 'object') {\n let sub_status;\n for (let sub_operator in directive) {\n sub_status = resolveStatus(statuses, directive[sub_operator], sub_operator);\n if (status === null) {\n status = sub_status;\n } else if (operator === 'and') {\n status = status && sub_status;\n } else if (operator === 'or') {\n status = status || sub_status;\n }\n }\n } else {\n return false;\n }\n return status;\n };\n\n let show_directive = {};\n if (typeof tooltip_layout.show == 'string') {\n show_directive = { and: [ tooltip_layout.show ] };\n } else if (typeof tooltip_layout.show == 'object') {\n show_directive = tooltip_layout.show;\n }\n\n let hide_directive = {};\n if (typeof tooltip_layout.hide == 'string') {\n hide_directive = { and: [ tooltip_layout.hide ] };\n } else if (typeof tooltip_layout.hide == 'object') {\n hide_directive = tooltip_layout.hide;\n }\n\n // Find all the statuses that apply to just this single element\n const _layer_state = this._layer_state;\n var status_flags = {}; // {status_name: bool}\n STATUSES.adjectives.forEach((status) => {\n const antistatus = `un${status}`;\n status_flags[status] = (_layer_state.status_flags[status].has(id));\n status_flags[antistatus] = !status_flags[status];\n });\n\n // Decide whether to show/hide the tooltip based solely on the underlying element\n const show_resolved = resolveStatus(status_flags, show_directive);\n const hide_resolved = resolveStatus(status_flags, hide_directive);\n\n // Most of the tooltip display logic depends on behavior layouts: was point (un)selected, (un)highlighted, etc.\n // But sometimes, a point is selected, and the user then closes the tooltip. If the panel is re-rendered for\n // some outside reason (like state change), we must track this in the create/destroy events as tooltip state.\n const has_tooltip = (_layer_state.status_flags['has_tooltip'].has(id));\n const tooltip_was_closed = first_time ? false : !has_tooltip;\n if (show_resolved && !tooltip_was_closed && !hide_resolved) {\n this.createTooltip(element);\n } else {\n this.destroyTooltip(element);\n }\n\n return this;\n }\n\n /**\n * Toggle a status (e.g. highlighted, selected, identified) on an element\n *\n * @private\n * @fires event:layout_changed\n * @fires event:element_selection\n * @fires event:match_requested\n * @param {String} status The name of a recognized status to be added/removed on an appropriate element\n * @param {String|Object} element The data bound to the element of interest\n * @param {Boolean} active True to add the status (and associated CSS styles); false to remove it\n * @param {Boolean} exclusive Whether to only allow a state for a single element at a time\n * @returns {BaseDataLayer}\n */\n setElementStatus(status, element, active, exclusive) {\n if (status === 'has_tooltip') {\n // This is a special adjective that exists solely to track tooltip state. It has no CSS and never gets set\n // directly. It is invisible to the official enums.\n return this;\n }\n if (typeof active == 'undefined') {\n active = true;\n }\n\n // Get an ID for the element or return having changed nothing\n let element_id;\n try {\n element_id = this.getElementId(element);\n } catch (get_element_id_error) {\n return this;\n }\n\n // Enforce exclusivity (force all elements to have the opposite of toggle first)\n if (exclusive) {\n this.setAllElementStatus(status, !active);\n }\n\n // Set/unset the proper status class on the appropriate DOM element(s), *and* potentially an additional element\n d3.select(`#${element_id}`).classed(`lz-data_layer-${this.layout.type}-${status}`, active);\n const element_status_node_id = this.getElementStatusNodeId(element);\n if (element_status_node_id !== null) {\n d3.select(`#${element_status_node_id}`).classed(`lz-data_layer-${this.layout.type}-statusnode-${status}`, active);\n }\n\n // Track element ID in the proper status state array\n const added_status = !this._layer_state.status_flags[status].has(element_id); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this._layer_state.status_flags[status].add(element_id);\n }\n if (!active && !added_status) {\n this._layer_state.status_flags[status].delete(element_id);\n }\n\n // Trigger tool tip show/hide logic\n this.showOrHideTooltip(element, added_status);\n\n // Trigger layout changed event hook\n if (added_status) {\n this.parent.emit('layout_changed', true);\n }\n\n const is_selected = (status === 'selected');\n if (is_selected && (added_status || !active)) {\n // Notify parents that an element has changed selection status (either active, or inactive)\n this.parent.emit('element_selection', { element: element, active: active }, true);\n }\n\n const value_to_broadcast = (this.layout.match && this.layout.match.send);\n if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) {\n this.parent.emit(\n // The broadcast value can use transforms to \"clean up value before sending broadcasting\"\n 'match_requested',\n { value: new Field(value_to_broadcast).resolve(element), active: active },\n true,\n );\n }\n return this;\n }\n\n /**\n * Toggle a status on all elements in the data layer\n *\n * @private\n * @param {String} status\n * @param {Boolean} toggle\n * @returns {BaseDataLayer}\n */\n setAllElementStatus(status, toggle) {\n\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Apply statuses\n if (toggle) {\n this.data.forEach((element) => this.setElementStatus(status, element, true));\n } else {\n const status_ids = new Set(this._layer_state.status_flags[status]); // copy so that we don't mutate while iterating\n status_ids.forEach((id) => {\n const element = this.getElementById(id);\n if (typeof element == 'object' && element !== null) {\n this.setElementStatus(status, element, false);\n }\n });\n this._layer_state.status_flags[status] = new Set();\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n return this;\n }\n\n /**\n * Apply all layout-defined behaviors (DOM event handlers) to a selection of elements\n *\n * @private\n * @param {d3.selection} selection\n */\n applyBehaviors(selection) {\n if (typeof this.layout.behaviors != 'object') {\n return;\n }\n Object.keys(this.layout.behaviors).forEach((directive) => {\n const event_match = /(click|mouseover|mouseout)/.exec(directive);\n if (!event_match) {\n return;\n }\n selection.on(`${event_match[0]}.${directive}`, this.executeBehaviors(directive, this.layout.behaviors[directive]));\n });\n }\n\n /**\n * Generate a function that executes an arbitrary list of behaviors on an element during an event\n *\n * @private\n * @param {String} directive The name of the event, as described in layout.behaviors for this datalayer\n * @param {Object[]} behaviors An object describing the behavior to attach to this single element\n * @param {string} behaviors.action The name of the action that would trigger this behavior (eg click, mouseover, etc)\n * @param {string} behaviors.status What status to apply to the element when this behavior is triggered (highlighted,\n * selected, etc)\n * @param {boolean} [behaviors.exclusive] Whether triggering the event for this element should unset the relevant status\n * for all other elements. Useful for, eg, click events that exclusively highlight one thing.\n * @returns {function(this:BaseDataLayer)} Return a function that handles the event in context with the behavior\n * and the element- can be attached as an event listener\n */\n executeBehaviors(directive, behaviors) {\n\n // Determine the required state of control and shift keys during the event\n const requiredKeyStates = {\n 'ctrl': (directive.includes('ctrl')),\n 'shift': (directive.includes('shift')),\n };\n const self = this;\n return function(element) {\n // This method may be used on two kinds of events: directly attached, or bubbled.\n // D3 doesn't natively support bubbling very well; if no data is bound on the currentTarget, check to see\n // if there is data available at wherever the event was initiated from\n element = element || d3.select(d3.event.target).datum();\n\n // Do nothing if the required control and shift key presses (or lack thereof) doesn't match the event\n if (requiredKeyStates.ctrl !== !!d3.event.ctrlKey || requiredKeyStates.shift !== !!d3.event.shiftKey) {\n return;\n }\n\n // Loop through behaviors making each one go in succession\n behaviors.forEach((behavior) => {\n\n // Route first by the action, if defined\n if (typeof behavior != 'object' || behavior === null) {\n return;\n }\n\n switch (behavior.action) {\n\n // Set a status (set to true regardless of current status, optionally with exclusivity)\n case 'set':\n self.setElementStatus(behavior.status, element, true, behavior.exclusive);\n break;\n\n // Unset a status (set to false regardless of current status, optionally with exclusivity)\n case 'unset':\n self.setElementStatus(behavior.status, element, false, behavior.exclusive);\n break;\n\n // Toggle a status\n case 'toggle':\n var current_status_boolean = (self._layer_state.status_flags[behavior.status].has(self.getElementId(element)));\n var exclusive = behavior.exclusive && !current_status_boolean;\n\n self.setElementStatus(behavior.status, element, !current_status_boolean, exclusive);\n break;\n\n // Link to a dynamic URL\n case 'link':\n if (typeof behavior.href == 'string') {\n const url = parseFields(behavior.href, element, self.getElementAnnotation(element));\n if (typeof behavior.target == 'string') {\n window.open(url, behavior.target);\n } else {\n window.location.href = url;\n }\n }\n break;\n\n // Action not defined, just return\n default:\n break;\n }\n });\n };\n }\n\n /**\n * Get an object with the x and y coordinates of the panel's origin in terms of the entire page\n * Necessary for positioning any HTML elements over the panel\n *\n * @private\n * @returns {{x: Number, y: Number}}\n */\n _getPageOrigin() {\n const panel_origin = this.parent._getPageOrigin();\n return {\n x: panel_origin.x + this.parent.layout.margin.left,\n y: panel_origin.y + this.parent.layout.margin.top,\n };\n }\n\n /**\n * Apply all tracked element statuses. This is primarily intended for re-rendering the plot, in order to preserve\n * behaviors when items are updated.\n * @private\n */\n applyAllElementStatus () {\n const status_flags = this._layer_state.status_flags;\n const self = this;\n for (let property in status_flags) {\n if (!Object.prototype.hasOwnProperty.call(status_flags, property)) {\n continue;\n }\n status_flags[property].forEach((element_id) => {\n try {\n this.setElementStatus(property, this.getElementById(element_id), true);\n } catch (e) {\n console.warn(`Unable to apply state: ${self._state_id}, ${property}`);\n console.error(e);\n }\n });\n\n }\n }\n\n /**\n * Position the datalayer and all tooltips\n * @private\n * @returns {BaseDataLayer}\n */\n draw() {\n this.svg.container\n .attr('transform', `translate(${this.parent.layout.cliparea.origin.x}, ${this.parent.layout.cliparea.origin.y})`);\n this.svg.clipRect\n .attr('width', this.parent.layout.cliparea.width)\n .attr('height', this.parent.layout.cliparea.height);\n this.positionAllTooltips();\n return this;\n }\n\n /**\n * Re-Map a data layer to reflect changes in the state of a plot (such as viewing region/ chromosome range)\n *\n * Whereas .render draws whatever data is available, this method resets the view and fetches new data if necessary.\n *\n * @private\n * @return {Promise}\n */\n reMap() {\n this.destroyAllTooltips(); // hack - only non-visible tooltips should be destroyed\n // and then recreated if returning to visibility\n\n // Fetch new data. Datalayers are only given access to the final consolidated data from the chain (not headers or raw payloads)\n return this.parent_plot.lzd.getData(this.state, this._entities, this._dependencies)\n .then((new_data) => {\n this.data = new_data;\n this.applyDataMethods();\n this._initialized = true;\n // Allow listeners (like subscribeToData) to see the information associated with a layer\n this.parent.emit(\n 'data_from_layer',\n { layer: this.getBaseId(), content: deepCopy(new_data) }, // TODO: revamp event signature post layer-eventing-mixin\n true,\n );\n });\n }\n}\n\nSTATUSES.verbs.forEach((verb, idx) => {\n const adjective = STATUSES.adjectives[idx];\n const antiverb = `un${verb}`;\n // Set/unset a single element's status\n\n /**\n * @private\n * @function highlightElement\n */\n /**\n * @private\n * @function selectElement\n */\n /**\n * @private\n * @function fadeElement\n */\n /**\n * @private\n * @function hideElement\n */\n BaseDataLayer.prototype[`${verb}Element`] = function(element, exclusive = false) {\n exclusive = !!exclusive;\n this.setElementStatus(adjective, element, true, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightElement\n */\n /**\n * @private\n * @function unselectElement\n */\n /**\n * @private\n * @function unfadeElement\n */\n /**\n * @private\n * @function unhideElement\n */\n BaseDataLayer.prototype[`${antiverb}Element`] = function(element, exclusive) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\n this.setElementStatus(adjective, element, false, exclusive);\n return this;\n };\n\n /**\n * @private\n * @function highlightAllElements\n */\n /**\n * @private\n * @function selectAllElements\n */\n /**\n * @private\n * @function fadeAllElements\n */\n /**\n * @private\n * @function hideAllElements\n */\n // Set/unset status for all elements\n BaseDataLayer.prototype[`${verb}AllElements`] = function() {\n this.setAllElementStatus(adjective, true);\n return this;\n };\n\n /**\n * @private\n * @function unhighlightAllElements\n */\n /**\n * @private\n * @function unselectAllElements\n */\n /**\n * @private\n * @function unfadeAllElements\n * */\n /**\n * @private\n * @function unhideAllElements\n */\n BaseDataLayer.prototype[`${antiverb}AllElements`] = function() {\n this.setAllElementStatus(adjective, false);\n return this;\n };\n});\n\nexport {BaseDataLayer as default};\n","import BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~annotation_track\n */\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical',\n hitarea_width: 8,\n};\n\n/**\n * Create a single continuous 2D track that provides information about each datapoint\n *\n * For example, this can be used to mark items by membership in a group, alongside information in other panels\n * @alias module:LocusZoom_DataLayers~annotation_track\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass AnnotationTrack extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color] Specify how to choose the fill color for each tick mark\n * @param {number} [layout.hitarea_width=8] The width (in pixels) of hitareas. Annotation marks are typically 1 px wide,\n * so a hit area of 4px on each side can make it much easier to select an item for a tooltip. Hitareas will not interfere\n * with selecting adjacent points.\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n if (!Array.isArray(layout.filters)) {\n throw new Error('Annotation track must specify array of filters for selecting points to annotate');\n }\n merge(layout, default_layout);\n super(...arguments);\n }\n\n initialize() {\n super.initialize();\n this._hitareas_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-hit_areas`);\n\n this._visible_lines_group = this.svg.group.append('g')\n .attr('class', `lz-data_layer-${this.layout.type}-visible_lines`);\n }\n\n render() {\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n const hit_areas_selection = this._hitareas_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n\n const _getX = (d, i) => {\n // Helper for hitarea position calcs: ensures that a hitarea never overlaps the space allocated\n // for a real data element. Helps to avoid mouse jitter when selecting tooltips in crowded areas.\n const x_center = this.parent['x_scale'](d[this.layout.x_axis.field]);\n let x_left = x_center - this.layout.hitarea_width / 2;\n if (i >= 1) {\n // This assumes that the data are in sorted order.\n const left_node = track_data[i - 1];\n const left_node_x_center = this.parent['x_scale'](left_node[this.layout.x_axis.field]);\n x_left = Math.max(x_left, (x_center + left_node_x_center) / 2);\n }\n return [x_left, x_center];\n };\n\n // Draw hitareas under real data elements, so that real data elements always take precedence\n hit_areas_selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n // Update the set of elements to reflect new data\n .merge(hit_areas_selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('height', this.parent.layout.height)\n .attr('opacity', 0)\n .attr('x', (d, i) => {\n const crds = _getX(d, i);\n return crds[0];\n })\n .attr('width', (d, i) => {\n const crds = _getX(d, i);\n return (crds[1] - crds[0]) + this.layout.hitarea_width / 2;\n });\n\n const width = 1;\n const selection = this._visible_lines_group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n // Draw rectangles (visual and tooltip positioning)\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => this.parent['x_scale'](d[this.layout.x_axis.field]) - width / 2)\n .attr('width', width)\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Set up tooltips and mouse interaction\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n // Remove unused elements\n hit_areas_selection.exit()\n .remove();\n }\n\n /**\n * Render tooltip at the center of each tick mark\n * @param tooltip\n * @return {{y_min: number, x_max: *, y_max: *, x_min: number}}\n * @private\n */\n _getTooltipPosition(tooltip) {\n const panel = this.parent;\n const data_layer_height = panel.layout.height - (panel.layout.margin.top + panel.layout.margin.bottom);\n const stroke_width = 1; // as defined in the default stylesheet\n\n const x_center = panel.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_center = data_layer_height / 2;\n return {\n x_min: x_center - stroke_width,\n x_max: x_center + stroke_width,\n y_min: y_center - panel.layout.margin.top,\n y_max: y_center + panel.layout.margin.bottom,\n };\n }\n}\n\nexport {AnnotationTrack as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~highlight_regions\n */\nconst default_layout = {\n color: '#CCCCCC',\n fill_opacity: 0.5,\n // By default, it will draw the regions shown.\n filters: null,\n // Most use cases will show a preset list of regions defined in the layout\n // (if empty, AND layout.fields is not, it could fetch from a data source instead)\n regions: [],\n id_field: 'id',\n start_field: 'start',\n end_field: 'end',\n merge_field: null,\n};\n\n/**\n * \"Highlight regions with rectangle\" data layer.\n * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.\n *\n * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).\n * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a\n * useful highlight tool to draw attention to intervals that contain interesting variants.\n *\n * This layer has several useful modes:\n * 1. Draw one or more specified rectangles as provided from:\n * A. Hard-coded layout (layout.regions)\n * B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields\n * 2. Fetch data from an external source, and only render the intervals that match criteria\n *\n * @alias module:LocusZoom_DataLayers~highlight_regions\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass HighlightRegions extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that\n * rectangles do not hide or interfere with adjacent elements.\n * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.\n * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.\n * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate\n * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate\n * @param {String} [layout.merge_field] If two intervals overlap, they can be \"merged\" based on a field that\n * identifies the category (eg, only rectangles of the same category will be merged).\n * This field must be present in order to trigger merge behavior. This is applied after filters.\n */\n constructor(layout) {\n merge(layout, default_layout);\n if (layout.interaction || layout.behaviors) {\n throw new Error('highlight_regions layer does not support mouse events');\n }\n\n if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {\n throw new Error('highlight_regions layer can specify \"regions\" in layout, OR external data \"fields\", but not both');\n }\n super(...arguments);\n }\n\n /**\n * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same\n * area of the plot (will automatically sort the data prior to rendering)\n *\n * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.\n * Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.\n * @param {Object[]} data\n * @return {Object[]}\n * @private\n */\n _mergeNodes(data) {\n const { end_field, merge_field, start_field } = this.layout;\n if (!merge_field) {\n return data;\n }\n\n // Ensure data is sorted by start field, with category as a tie breaker\n data.sort((a, b) => {\n // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)\n return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);\n });\n\n let track_data = [];\n data.forEach(function (cur_item, index) {\n const prev_item = track_data[track_data.length - 1] || cur_item;\n if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {\n // If intervals overlap, merge the current item with the previous, and append only the merged interval\n const new_start = Math.min(prev_item[start_field], cur_item[start_field]);\n const new_end = Math.max(prev_item[end_field], cur_item[end_field]);\n cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });\n track_data.pop();\n }\n track_data.push(cur_item);\n });\n return track_data;\n }\n\n render() {\n const { x_scale } = this.parent;\n // Apply filters to only render a specified set of points\n let track_data = this.layout.regions.length ? this.layout.regions : this.data;\n\n // Pseudo identifier for internal use only (regions have no semantic or transition meaning)\n track_data.forEach((d, i) => d.id || (d.id = i));\n track_data = this._applyFilters(track_data);\n track_data = this._mergeNodes(track_data);\n\n const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)\n .data(track_data);\n\n // Draw rectangles\n selection.enter()\n .append('rect')\n .attr('class', `lz-data_layer-${this.layout.type}`)\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('x', (d) => x_scale(d[this.layout.start_field]))\n .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))\n .attr('height', this.parent.layout.height)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));\n\n // Remove unused elements\n selection.exit()\n .remove();\n\n // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom\n this.svg.group.style('pointer-events', 'none');\n }\n\n _getTooltipPosition(tooltip) {\n // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips\n throw new Error('This layer does not support tooltips');\n }\n}\n\nexport {HighlightRegions as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~arcs\n */\nconst default_layout = {\n color: 'seagreen',\n hitarea_width: '10px',\n style: {\n fill: 'none',\n 'stroke-width': '1px',\n 'stroke-opacity': '100%',\n },\n tooltip_positioning: 'top',\n};\n\n/**\n * Arc Data Layer\n * Implements a data layer that will render chromatin accessibility tracks.\n * This layer draws arcs (one per datapoint) that connect two endpoints (x.field1 and x.field2) by means of an arc,\n * with a height determined by y.field.\n * @alias module:LocusZoom_DataLayers~arcs\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Arcs extends BaseDataLayer {\n /**\n * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='seagreen'] Specify how to choose the stroke color for each arc\n * @param {number} [layout.hitarea_width='10px'] The width (in pixels) of hitareas. Arcs are only as wide as the stroke,\n * so a hit area of 5px on each side can make it much easier to select an item for a tooltip.\n * @param {string} [layout.style.fill='none'] The fill color under the area of the arc\n * @param {string} [layout.style.stroke-width='1px']\n * @param {string} [layout.style.stroke_opacity='100%']\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n * @param {string} [layout.x_axis.field1] The field to use for one end of the arc; creates a point at (x1, 0)\n * @param {string} [layout.x_axis.field2] The field to use for the other end of the arc; creates a point at (x2, 0)\n * @param {string} [layout.y_axis.field] The height at the midpoint of the arc, (xmid, y)\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n }\n\n // Implement the main render function\n render() {\n const self = this;\n const layout = self.layout;\n const x_scale = self.parent['x_scale'];\n const y_scale = self.parent[`y${layout.y_axis.axis}_scale`];\n\n // Apply filters to only render a specified set of points\n const track_data = this._applyFilters();\n\n // Helper: Each individual data point describes a path composed of 3 points, with a spline to smooth the line\n function _make_line(d) {\n const x1 = d[layout.x_axis.field1];\n const x2 = d[layout.x_axis.field2];\n const xmid = (x1 + x2) / 2;\n const coords = [\n [x_scale(x1), y_scale(0)],\n [x_scale(xmid), y_scale(d[layout.y_axis.field])],\n [x_scale(x2), y_scale(0)],\n ];\n // Smoothing options: https://bl.ocks.org/emmasaunders/f7178ed715a601c5b2c458a2c7093f78\n const line = d3.line()\n .x((d) => d[0])\n .y((d) => d[1])\n .curve(d3.curveNatural);\n return line(coords);\n }\n\n // Draw real lines, and also invisible hitareas for easier mouse events\n const hitareas = this.svg.group\n .selectAll('path.lz-data_layer-arcs-hitarea')\n .data(track_data, (d) => this.getElementId(d));\n\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-arcs')\n .data(track_data, (d) => this.getElementId(d));\n\n this.svg.group\n .call(applyStyles, layout.style);\n\n hitareas\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs-hitarea')\n .merge(hitareas)\n .attr('id', (d) => this.getElementId(d))\n .style('fill', 'none')\n .style('stroke-width', layout.hitarea_width)\n .style('stroke-opacity', 0)\n .style('stroke', 'transparent')\n .attr('d', (d) => _make_line(d));\n\n // Add new points as necessary\n selection\n .enter()\n .append('path')\n .attr('class', 'lz-data_layer-arcs')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .attr('stroke', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('d', (d, i) => _make_line(d));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n hitareas.exit()\n .remove();\n\n // Apply mouse behaviors to arcs\n this.svg.group\n .call(this.applyBehaviors.bind(this));\n\n return this;\n }\n\n _getTooltipPosition(tooltip) {\n // Center the tooltip arrow at the apex of the arc. Sometimes, only part of an arc shows on the screen, so we\n // clean up these values to ensure that the tooltip will appear within the window.\n const panel = this.parent;\n const layout = this.layout;\n\n const x1 = tooltip.data[layout.x_axis.field1];\n const x2 = tooltip.data[layout.x_axis.field2];\n\n const y_scale = panel[`y${layout.y_axis.axis}_scale`];\n\n return {\n x_min: panel.x_scale(Math.min(x1, x2)),\n x_max: panel.x_scale(Math.max(x1, x2)),\n y_min: y_scale(tooltip.data[layout.y_axis.field]),\n y_max: y_scale(0),\n };\n }\n\n}\n\nexport {Arcs as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\n/**\n * @memberof module:LocusZoom_DataLayers~genes\n * @type {{track_vertical_spacing: number, bounding_box_padding: number, color: string, tooltip_positioning: string, exon_height: number, label_font_size: number, label_exon_spacing: number, stroke: string}}\n */\nconst default_layout = {\n // Optionally specify different fill and stroke properties\n stroke: 'rgb(54, 54, 150)',\n color: '#363696',\n label_font_size: 15,\n label_exon_spacing: 3,\n exon_height: 10,\n bounding_box_padding: 3,\n track_vertical_spacing: 5,\n tooltip_positioning: 'top',\n};\n\n\n/**\n * Genes Data Layer\n * Implements a data layer that will render gene tracks\n * @alias module:LocusZoom_DataLayers~genes\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass Genes extends BaseDataLayer {\n /**\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.stroke='rgb(54, 54, 150)'] The stroke color for each intron and exon\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#363696'] The fill color for each intron and exon\n * @param {number} [layout.label_font_size]\n * @param {number} [layout.label_exon_spacing] The number of px padding between exons and the gene label\n * @param {number} [layout.exon_height=10] The height of each exon (vertical line) when drawing the gene\n * @param {number} [layout.bounding_box_padding=3] Padding around edges of the bounding box, as shown when highlighting a selected gene\n * @param {number} [layout.track_vertical_spacing=5] Vertical spacing between each row of genes\n * @param {'horizontal'|'vertical'|'top'|'bottom'|'left'|'right'} [layout.tooltip_positioning='top'] Where to draw the tooltip relative to the datum.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n super(...arguments);\n /**\n * A gene may have arbitrarily many transcripts, but this data layer isn't set up to render them yet.\n * Stash a transcript_idx to point to the first transcript and use that for all transcript refs.\n * @member {number}\n * @type {number}\n */\n this.transcript_idx = 0;\n\n /**\n * An internal counter for the number of tracks in the data layer. Used as an internal counter for looping\n * over positions / assignments\n * @protected\n * @member {number}\n */\n this.tracks = 1;\n\n /**\n * Store information about genes in dataset, in a hash indexed by track number: {track_number: [gene_indices]}\n * @member {Object.}\n */\n this.gene_track_index = { 1: [] };\n }\n\n /**\n * Generate a statusnode ID for a given element\n * @override\n * @returns {String}\n */\n getElementStatusNodeId(element) {\n return `${this.getElementId(element)}-statusnode`;\n }\n\n /**\n * Helper function to sum layout values to derive total height for a single gene track\n * @returns {number}\n */\n getTrackHeight() {\n return 2 * this.layout.bounding_box_padding\n + this.layout.label_font_size\n + this.layout.label_exon_spacing\n + this.layout.exon_height\n + this.layout.track_vertical_spacing;\n }\n\n /**\n * Ensure that genes in overlapping chromosome regions are positioned so that parts of different genes do not\n * overlap in the view. A track is a row used to vertically separate overlapping genes.\n * @returns {Genes}\n */\n assignTracks(data) {\n /**\n * Function to get the width in pixels of a label given the text and layout attributes\n * @param {String} gene_name\n * @param {number|string} font_size\n * @returns {number}\n */\n const _getLabelWidth = (gene_name, font_size) => {\n try {\n const temp_text = this.svg.group.append('text')\n .attr('x', 0)\n .attr('y', 0)\n .attr('class', 'lz-data_layer-genes lz-label')\n .style('font-size', font_size)\n .text(`${gene_name}→`);\n const label_width = temp_text.node().getBBox().width;\n temp_text.remove();\n return label_width;\n } catch (e) {\n return 0;\n }\n };\n\n // Reinitialize some metadata\n this.tracks = 1;\n this.gene_track_index = { 1: [] };\n\n return data\n // Filter out any genes that are fully outside the region of interest. This allows us to use cached data\n // when zooming in, without breaking the layout by allocating space for genes that are not visible.\n .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end))\n .map((item) => {\n // If necessary, split combined gene id / version fields into discrete fields.\n // NOTE: this may be an issue with CSG's genes data API that may eventually be solved upstream.\n if (item.gene_id && item.gene_id.indexOf('.')) {\n const split = item.gene_id.split('.');\n item.gene_id = split[0];\n item.gene_version = split[1];\n }\n\n // Stash the transcript ID on the parent gene\n item.transcript_id = item.transcripts[this.transcript_idx].transcript_id;\n\n // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see\n // (range: values in terms of pixels on the screen)\n item.display_range = {\n start: this.parent.x_scale(Math.max(item.start, this.state.start)),\n end: this.parent.x_scale(Math.min(item.end, this.state.end)),\n };\n item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size);\n item.display_range.width = item.display_range.end - item.display_range.start;\n // Determine label text anchor (default to middle)\n item.display_range.text_anchor = 'middle';\n if (item.display_range.width < item.display_range.label_width) {\n if (item.start < this.state.start) {\n item.display_range.end = item.display_range.start\n + item.display_range.label_width\n + this.layout.label_font_size;\n item.display_range.text_anchor = 'start';\n } else if (item.end > this.state.end) {\n item.display_range.start = item.display_range.end\n - item.display_range.label_width\n - this.layout.label_font_size;\n item.display_range.text_anchor = 'end';\n } else {\n const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2)\n + this.layout.label_font_size;\n if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) {\n item.display_range.start = this.parent.x_scale(this.state.start);\n item.display_range.end = item.display_range.start + item.display_range.label_width;\n item.display_range.text_anchor = 'start';\n } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) {\n item.display_range.end = this.parent.x_scale(this.state.end);\n item.display_range.start = item.display_range.end - item.display_range.label_width;\n item.display_range.text_anchor = 'end';\n } else {\n item.display_range.start -= centered_margin;\n item.display_range.end += centered_margin;\n }\n }\n item.display_range.width = item.display_range.end - item.display_range.start;\n }\n // Add bounding box padding to the calculated display range start, end, and width\n item.display_range.start -= this.layout.bounding_box_padding;\n item.display_range.end += this.layout.bounding_box_padding;\n item.display_range.width += 2 * this.layout.bounding_box_padding;\n // Convert and stash display range values into domain values\n // (domain: values in terms of the data set, e.g. megabases)\n item.display_domain = {\n start: this.parent.x_scale.invert(item.display_range.start),\n end: this.parent.x_scale.invert(item.display_range.end),\n };\n item.display_domain.width = item.display_domain.end - item.display_domain.start;\n\n // Using display range/domain data generated above cast each gene to tracks such that none overlap\n item.track = null;\n let potential_track = 1;\n while (item.track === null) {\n let collision_on_potential_track = false;\n this.gene_track_index[potential_track].map((placed_gene) => {\n if (!collision_on_potential_track) {\n const min_start = Math.min(placed_gene.display_range.start, item.display_range.start);\n const max_end = Math.max(placed_gene.display_range.end, item.display_range.end);\n if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) {\n collision_on_potential_track = true;\n }\n }\n });\n if (!collision_on_potential_track) {\n item.track = potential_track;\n this.gene_track_index[potential_track].push(item);\n } else {\n potential_track++;\n if (potential_track > this.tracks) {\n this.tracks = potential_track;\n this.gene_track_index[potential_track] = [];\n }\n }\n }\n\n // Stash parent references on all genes, transcripts, and exons\n item.parent = this;\n item.transcripts.map((d, t) => {\n item.transcripts[t].parent = item;\n item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]);\n });\n return item;\n });\n }\n\n /**\n * Main render function\n */\n render() {\n const self = this;\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n track_data = this.assignTracks(track_data);\n let height;\n\n // Render gene groups\n const selection = this.svg.group.selectAll('g.lz-data_layer-genes')\n .data(track_data, (d) => d.gene_name);\n\n selection.enter()\n .append('g')\n .attr('class', 'lz-data_layer-genes')\n .merge(selection)\n .attr('id', (d) => this.getElementId(d))\n .each(function(gene) {\n const data_layer = gene.parent;\n\n // Render gene bounding boxes (status nodes to show selected/highlighted)\n const bboxes = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-data_layer-genes-statusnode')\n .data([gene], (d) => data_layer.getElementStatusNodeId(d));\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n\n bboxes.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-data_layer-genes-statusnode')\n .merge(bboxes)\n .attr('id', (d) => data_layer.getElementStatusNodeId(d))\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n bboxes.exit()\n .remove();\n\n // Render gene boundaries\n const boundaries = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-boundary')\n .data([gene], (d) => `${d.gene_name}_boundary`);\n\n // FIXME: Make gene text font sizes scalable\n height = 1;\n boundaries.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-boundary')\n .merge(boundaries)\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', (d) => {\n return ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing\n + (Math.max(data_layer.layout.exon_height, 3) / 2);\n })\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d, i));\n\n boundaries.exit()\n .remove();\n\n // Render gene labels\n const labels = d3.select(this).selectAll('text.lz-data_layer-genes.lz-label')\n .data([gene], (d) => `${d.gene_name}_label`);\n\n labels.enter()\n .append('text')\n .attr('class', 'lz-data_layer-genes lz-label')\n .merge(labels)\n .attr('text-anchor', (d) => d.display_range.text_anchor)\n .text((d) => (d.strand === '+') ? `${d.gene_name}→` : `←${d.gene_name}`)\n .style('font-size', gene.parent.layout.label_font_size)\n .attr('x', (d) => {\n if (d.display_range.text_anchor === 'middle') {\n return d.display_range.start + (d.display_range.width / 2);\n } else if (d.display_range.text_anchor === 'start') {\n return d.display_range.start + data_layer.layout.bounding_box_padding;\n } else if (d.display_range.text_anchor === 'end') {\n return d.display_range.end - data_layer.layout.bounding_box_padding;\n }\n })\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size,\n );\n\n labels.exit()\n .remove();\n\n // Render exon rects (first transcript only, for now)\n // Exons: by default color on gene properties for consistency with the gene boundary track- hence color uses d.parent.parent\n const exons = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-exon')\n .data(gene.transcripts[gene.parent.transcript_idx].exons, (d) => d.exon_id);\n\n height = data_layer.layout.exon_height;\n\n exons.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-exon')\n .merge(exons)\n .style('fill', (d, i) => self.resolveScalableParameter(self.layout.color, d.parent.parent, i))\n .style('stroke', (d, i) => self.resolveScalableParameter(self.layout.stroke, d.parent.parent, i))\n .attr('width', (d) => data_layer.parent.x_scale(d.end) - data_layer.parent.x_scale(d.start))\n .attr('height', height)\n .attr('x', (d) => data_layer.parent.x_scale(d.start))\n .attr('y', () => {\n return ((gene.track - 1) * data_layer.getTrackHeight())\n + data_layer.layout.bounding_box_padding\n + data_layer.layout.label_font_size\n + data_layer.layout.label_exon_spacing;\n });\n\n exons.exit()\n .remove();\n\n // Render gene click area\n const clickareas = d3.select(this).selectAll('rect.lz-data_layer-genes.lz-clickarea')\n .data([gene], (d) => `${d.gene_name}_clickarea`);\n\n height = data_layer.getTrackHeight() - data_layer.layout.track_vertical_spacing;\n clickareas.enter()\n .append('rect')\n .attr('class', 'lz-data_layer-genes lz-clickarea')\n .merge(clickareas)\n .attr('id', (d) => `${data_layer.getElementId(d)}_clickarea`)\n .attr('rx', data_layer.layout.bounding_box_padding)\n .attr('ry', data_layer.layout.bounding_box_padding)\n .attr('width', (d) => d.display_range.width)\n .attr('height', height)\n .attr('x', (d) => d.display_range.start)\n .attr('y', (d) => ((d.track - 1) * data_layer.getTrackHeight()));\n\n // Remove old clickareas as needed\n clickareas.exit()\n .remove();\n });\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply mouse behaviors & events to clickareas\n this.svg.group\n .on('click.event_emitter', (element) => this.parent.emit('element_clicked', element, true))\n .call(this.applyBehaviors.bind(this));\n }\n\n _getTooltipPosition(tooltip) {\n const gene_bbox_id = this.getElementStatusNodeId(tooltip.data);\n const gene_bbox = d3.select(`#${gene_bbox_id}`).node().getBBox();\n return {\n x_min: this.parent.x_scale(tooltip.data.start),\n x_max: this.parent.x_scale(tooltip.data.end),\n y_min: gene_bbox.y,\n y_max: gene_bbox.y + gene_bbox.height,\n };\n }\n}\n\nexport {Genes as default};\n","import * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\n\n/**\n * @memberof module:LocusZoom_DataLayers~line\n */\nconst default_layout = {\n style: {\n fill: 'none',\n 'stroke-width': '2px',\n },\n interpolate: 'curveLinear',\n x_axis: { field: 'x' },\n y_axis: { field: 'y', axis: 1 },\n hitarea_width: 5,\n tooltip: null,\n};\n\n/*********************\n * Line Data Layer\n * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.\n * @alias module:LocusZoom_DataLayers~line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n*/\nclass Line extends BaseDataLayer {\n /**\n * @param {object} [layout.style] CSS properties to control how the line is drawn\n * @param {string} [layout.style.fill='none'] Fill color for the area under the curve\n * @param {string} [layout.style.stroke]\n * @param {string} [layout.style.stroke-width='2px']\n * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.\n * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n if (layout.tooltip) {\n throw new Error('The line / filled curve layer does not support tooltips');\n }\n super(...arguments);\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_field = this.layout.x_axis.field;\n const y_field = this.layout.y_axis.field;\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line');\n\n // Generate the line\n let line;\n const x_scale = panel['x_scale'];\n const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];\n if (this.layout.style.fill && this.layout.style.fill !== 'none') {\n // Filled curve: define the line as a filled boundary\n line = d3.area()\n .x((d) => +x_scale(d[x_field]))\n .y0(+y_scale(0))\n .y1((d) => +y_scale(d[y_field]));\n } else {\n // Basic line\n line = d3.line()\n .x((d) => +x_scale(d[x_field]))\n .y((d) => +y_scale(d[y_field]))\n .curve(d3[this.layout.interpolate]);\n }\n\n // Apply line and style\n selection.merge(this.path)\n .attr('d', line)\n .call(applyStyles, this.layout.style);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n }\n\n /**\n * Redefine setElementStatus family of methods as line data layers will only ever have a single path element\n * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`\n * @param {String|Object} element\n * @param {Boolean} toggle\n */\n setElementStatus(status, element, toggle) {\n return this.setAllElementStatus(status, toggle);\n }\n\n setAllElementStatus(status, toggle) {\n // Sanity check\n if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {\n throw new Error('Invalid status');\n }\n if (typeof this._layer_state.status_flags[status] == 'undefined') {\n return this;\n }\n if (typeof toggle == 'undefined') {\n toggle = true;\n }\n\n // Update global status flag\n this._global_statuses[status] = toggle;\n\n // Apply class to path based on global status flags\n let path_class = 'lz-data_layer-line';\n Object.keys(this._global_statuses).forEach((global_status) => {\n if (this._global_statuses[global_status]) {\n path_class += ` lz-data_layer-line-${global_status}`;\n }\n });\n this.path.attr('class', path_class);\n\n // Trigger layout changed event hook\n this.parent.emit('layout_changed', true);\n return this;\n }\n}\n\n/**\n * @memberof module:LocusZoom_DataLayers~orthogonal_line\n */\nconst default_orthogonal_layout = {\n style: {\n 'stroke': '#D3D3D3',\n 'stroke-width': '3px',\n 'stroke-dasharray': '10px 10px',\n },\n orientation: 'horizontal',\n x_axis: {\n axis: 1,\n decoupled: true,\n },\n y_axis: {\n axis: 1,\n decoupled: true,\n },\n tooltip_positioning: 'vertical',\n offset: 0,\n};\n\n\n/**\n * Orthogonal Line Data Layer\n * Draw a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source or fields.\n * @alias module:LocusZoom_DataLayers~orthogonal_line\n * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options\n */\nclass OrthogonalLine extends BaseDataLayer {\n /**\n * @param {string} [layout.style.stroke='#D3D3D3']\n * @param {string} [layout.style.stroke-width='3px']\n * @param {string} [layout.style.stroke-dasharray='10px 10px']\n * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line\n * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.\n * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.\n * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.\n * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)\n */\n constructor(layout) {\n layout = merge(layout, default_orthogonal_layout);\n // Require that orientation be \"horizontal\" or \"vertical\" only\n if (!['horizontal', 'vertical'].includes(layout.orientation)) {\n layout.orientation = 'horizontal';\n }\n super(...arguments);\n }\n\n getElementId(element) {\n // There is only one line per datalayer, so this is sufficient.\n return this.getBaseId();\n }\n\n /**\n * Implement the main render function\n */\n render() {\n // Several vars needed to be in scope\n const panel = this.parent;\n const x_scale = 'x_scale';\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const x_extent = 'x_extent';\n const y_extent = `y${this.layout.y_axis.axis}_extent`;\n const x_range = 'x_range';\n\n // Generate data using extents depending on orientation\n if (this.layout.orientation === 'horizontal') {\n this.data = [\n { x: panel[x_extent][0], y: this.layout.offset },\n { x: panel[x_extent][1], y: this.layout.offset },\n ];\n } else if (this.layout.orientation === 'vertical') {\n this.data = [\n { x: this.layout.offset, y: panel[y_extent][0] },\n { x: this.layout.offset, y: panel[y_extent][1] },\n ];\n } else {\n throw new Error('Unrecognized vertical line type. Must be \"vertical\" or \"horizontal\"');\n }\n\n // Join data to the line selection\n const selection = this.svg.group\n .selectAll('path.lz-data_layer-line')\n .data([this.data]);\n\n // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)\n // When that happens, provide a default height based on the current panel dimensions (accounting\n // for any resizing that happened after the panel was created)\n const default_y = [panel.layout.cliparea.height, 0];\n\n // Generate the line\n const line = d3.line()\n .x((d, i) => {\n const x = +panel[x_scale](d['x']);\n return isNaN(x) ? panel[x_range][i] : x;\n })\n .y((d, i) => {\n const y = +panel[y_scale](d['y']);\n return isNaN(y) ? default_y[i] : y;\n });\n\n // Create path element, apply class\n this.path = selection.enter()\n .append('path')\n .attr('class', 'lz-data_layer-line')\n .merge(selection)\n .attr('d', line)\n .call(applyStyles, this.layout.style)\n // Allow the layer to respond to mouseover events and show a tooltip.\n .call(this.applyBehaviors.bind(this));\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n }\n\n _getTooltipPosition(tooltip) {\n try {\n const coords = d3.mouse(this.svg.container.node());\n const x = coords[0];\n const y = coords[1];\n return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };\n } catch (e) {\n // On redraw, there won't be a mouse event, so skip tooltip repositioning.\n return null;\n }\n }\n\n}\n\n\nexport { Line as line, OrthogonalLine as orthogonal_line };\n","import * as d3 from 'd3';\nimport BaseDataLayer from './base';\nimport {applyStyles} from '../../helpers/common';\nimport {parseFields} from '../../helpers/display';\nimport {merge, nameToSymbol} from '../../helpers/layouts';\nimport {coalesce_scatter_points} from '../../helpers/render';\n\n/**\n * @memberof module:LocusZoom_DataLayers~scatter\n */\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n active: false,\n max_points: 800, // Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width\n // Define the \"region of interest\", like \"bottom half of plot\"; any points outside this region are taken as is\n // Values are expressed in terms of data value and will be converted to pixels internally.\n x_min: '-Infinity', // JSON doesn't handle some valid JS numbers. Kids, don't get a career in computers.\n x_max: 'Infinity',\n y_min: 0,\n y_max: 3.0,\n x_gap: 7,\n y_gap: 7,\n },\n fill_opacity: 1,\n y_axis: {\n axis: 1,\n },\n id_field: 'id',\n};\n\n/**\n * Options that control point-coalescing in scatter plots\n * @typedef {object} module:LocusZoom_DataLayers~scatter~coalesce_options\n * @property {boolean} [active=false] Whether to use this feature. Typically used for GWAS plots, but\n * not other scatter plots such as PheWAS.\n * @property {number} [max_points=800] Only attempt to reduce DOM size if there are at least this many\n * points. Many plots are 800-2400 px wide, so, more than 1 datum per pixel of average region width. For more\n * sparse datasets, all points will be faithfully rendered even if coalesce.active=true.\n * @property {number} [x_min='-Infinity'] Min x coordinate of the region where points will be coalesced\n * @property {number} [x_max='Infinity'] Max x coordinate of the region where points will be coalesced\n * @property {number} [y_min=0] Min y coordinate of the region where points will be coalesced.\n * @property {number} [y_max=3.0] Max y coordinate of the region where points will be coalesced\n * @property {number} [x_gap=7] Max number of pixels between the center of two points that can be\n * coalesced. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\n * @property {number} [y_gap=7]\n */\n\n/**\n * Scatter Data Layer\n * Implements a standard scatter plot\n * @alias module:LocusZoom_DataLayers~scatter\n */\nclass Scatter extends BaseDataLayer {\n /**\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_size=40] The size (area) of the point for each datum\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.point_shape='circle'] Shape of the point for each datum. Supported values map to the d3 SVG Symbol Types (i.e.: \"circle\", \"cross\", \"diamond\", \"square\", \"triangle\", \"star\", and \"wye\"), plus \"triangledown\".\n * @param {string|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#888888'] The color of the point for each datum\n * @param {module:LocusZoom_DataLayers~scatter~coalesce_options} [layout.coalesce] Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n * to improve rendering performance. These options are primarily aimed at GWAS region plots. Within a specified\n * rectangle area (eg \"insignificant point cutoff\"), we choose only points far enough part to be seen.\n * The defaults are specifically tuned for GWAS plots with -log(p) on the y-axis.\n * @param {number|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=1] Opacity (0..1) for each datum point\n * @param {string} [layout.label.text] Similar to tooltips: a template string that can reference datum fields for label text.\n * @param {number} [layout.label.spacing] Distance (in px) between the label and the center of the datum.\n * @param {object} [layout.label.lines.style] CSS style options for how the line is rendered\n * @param {number} [layout.label.filters] Filters that describe which points to label. For performance reasons,\n * we recommend labeling only a small subset of most interesting points.\n * @param {object} [layout.label.style] CSS style options for label text\n */\n constructor(layout) {\n layout = merge(layout, default_layout);\n\n // Extra default for layout spacing\n // Not in default layout since that would make the label attribute always present\n if (layout.label && isNaN(layout.label.spacing)) {\n layout.label.spacing = 4;\n }\n super(...arguments);\n }\n\n // Implement tooltip position to be layer-specific\n _getTooltipPosition(tooltip) {\n const x_center = this.parent.x_scale(tooltip.data[this.layout.x_axis.field]);\n const y_scale = `y${this.layout.y_axis.axis}_scale`;\n const y_center = this.parent[y_scale](tooltip.data[this.layout.y_axis.field]);\n const point_size = this.resolveScalableParameter(this.layout.point_size, tooltip.data);\n const offset = Math.sqrt(point_size / Math.PI);\n\n return {\n x_min: x_center - offset, x_max: x_center + offset,\n y_min: y_center - offset, y_max: y_center + offset,\n };\n }\n\n // Function to flip labels from being anchored at the start of the text to the end\n // Both to keep labels from running outside the data layer and also as a first\n // pass on recursive separation\n flip_labels() {\n const data_layer = this;\n // Base positions on the default point size (which is what resolve scalable param returns if no data provided)\n const point_size = data_layer.resolveScalableParameter(data_layer.layout.point_size, {});\n const spacing = data_layer.layout.label.spacing;\n const handle_lines = Boolean(data_layer.layout.label.lines);\n const min_x = 2 * spacing;\n const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing);\n\n const flip = (dn, dnl) => {\n const dnx = +dn.attr('x');\n const text_swing = (2 * spacing) + (2 * Math.sqrt(point_size));\n let dnlx2;\n let line_swing;\n if (handle_lines) {\n dnlx2 = +dnl.attr('x2');\n line_swing = spacing + (2 * Math.sqrt(point_size));\n }\n if (dn.style('text-anchor') === 'start') {\n dn.style('text-anchor', 'end');\n dn.attr('x', dnx - text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 - line_swing);\n }\n } else {\n dn.style('text-anchor', 'start');\n dn.attr('x', dnx + text_swing);\n if (handle_lines) {\n dnl.attr('x2', dnlx2 + line_swing);\n }\n }\n };\n // Flip any going over the right edge from the right side to the left side\n // (all labels start on the right side)\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n const dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n if (dax + abound.width + spacing > max_x) {\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n flip(da, dal);\n }\n });\n // Second pass to flip any others that haven't flipped yet if they collide with another label\n data_layer._label_texts.each(function (d, i) {\n const a = this;\n const da = d3.select(a);\n if (da.style('text-anchor') === 'end') {\n return;\n }\n let dax = +da.attr('x');\n const abound = da.node().getBoundingClientRect();\n const dal = handle_lines ? d3.select(data_layer._label_lines.nodes()[i]) : null;\n data_layer._label_texts.each(function () {\n const b = this;\n const db = d3.select(b);\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (collision) {\n flip(da, dal);\n // Double check that this flip didn't push the label past min_x. If it did, immediately flip back.\n dax = +da.attr('x');\n if (dax - abound.width - spacing < min_x) {\n flip(da, dal);\n }\n }\n });\n });\n }\n\n // Recursive function to space labels apart immediately after initial render\n // Adapted from thudfactor's fiddle here: https://jsfiddle.net/thudfactor/HdwTH/\n // TODO: Make labels also aware of data elements\n separate_labels() {\n this._label_iterations++;\n const data_layer = this;\n const alpha = 0.5;\n if (!this.layout.label) {\n // Guard against layout changing in the midst of iterative rerender\n return;\n }\n const spacing = this.layout.label.spacing;\n let again = false;\n data_layer._label_texts.each(function () {\n // TODO: O(n2) algorithm; revisit performance?\n const a = this;\n const da = d3.select(a);\n const y1 = da.attr('y');\n data_layer._label_texts.each(function () {\n const b = this;\n // a & b are the same element and don't collide.\n if (a === b) {\n return;\n }\n const db = d3.select(b);\n // a & b are on opposite sides of the chart and\n // don't collide\n if (da.attr('text-anchor') !== db.attr('text-anchor')) {\n return;\n }\n // Determine if the bounding rects for the two text elements collide\n const abound = da.node().getBoundingClientRect();\n const bbound = db.node().getBoundingClientRect();\n const collision = abound.left < bbound.left + bbound.width + (2 * spacing) &&\n abound.left + abound.width + (2 * spacing) > bbound.left &&\n abound.top < bbound.top + bbound.height + (2 * spacing) &&\n abound.height + abound.top + (2 * spacing) > bbound.top;\n if (!collision) {\n return;\n }\n again = true;\n // If the labels collide, we'll push each\n // of the two labels up and down a little bit.\n const y2 = db.attr('y');\n const sign = abound.top < bbound.top ? 1 : -1;\n const adjust = sign * alpha;\n let new_a_y = +y1 - adjust;\n let new_b_y = +y2 + adjust;\n // Keep new values from extending outside the data layer\n const min_y = 2 * spacing;\n const max_y = data_layer.parent.layout.height - data_layer.parent.layout.margin.top - data_layer.parent.layout.margin.bottom - (2 * spacing);\n let delta;\n if (new_a_y - (abound.height / 2) < min_y) {\n delta = +y1 - new_a_y;\n new_a_y = +y1;\n new_b_y += delta;\n } else if (new_b_y - (bbound.height / 2) < min_y) {\n delta = +y2 - new_b_y;\n new_b_y = +y2;\n new_a_y += delta;\n }\n if (new_a_y + (abound.height / 2) > max_y) {\n delta = new_a_y - +y1;\n new_a_y = +y1;\n new_b_y -= delta;\n } else if (new_b_y + (bbound.height / 2) > max_y) {\n delta = new_b_y - +y2;\n new_b_y = +y2;\n new_a_y -= delta;\n }\n da.attr('y', new_a_y);\n db.attr('y', new_b_y);\n });\n });\n if (again) {\n // Adjust lines to follow the labels\n if (data_layer.layout.label.lines) {\n const label_elements = data_layer._label_texts.nodes();\n data_layer._label_lines.attr('y2', (d, i) => {\n const label_line = d3.select(label_elements[i]);\n return label_line.attr('y');\n });\n }\n // After ~150 iterations we're probably beyond diminising returns, so stop recursing\n if (this._label_iterations < 150) {\n setTimeout(() => {\n this.separate_labels();\n }, 1);\n }\n }\n }\n\n // Implement the main render function\n render() {\n const data_layer = this;\n const x_scale = this.parent['x_scale'];\n const y_scale = this.parent[`y${this.layout.y_axis.axis}_scale`];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n // Apply filters to only render a specified set of points\n let track_data = this._applyFilters();\n\n // Add coordinates before rendering, so we can coalesce\n track_data.forEach((item) => {\n let x = x_scale(item[this.layout.x_axis.field]);\n let y = y_scale(item[this.layout.y_axis.field]);\n if (isNaN(x)) {\n x = -1000;\n }\n if (isNaN(y)) {\n y = -1000;\n }\n item[xcs] = x;\n item[ycs] = y;\n });\n\n if (this.layout.coalesce.active && track_data.length > this.layout.coalesce.max_points) {\n let { x_min, x_max, y_min, y_max, x_gap, y_gap } = this.layout.coalesce;\n // Convert x and y \"significant region\" range from data values to pixels\n const x_min_px = isFinite(x_min) ? x_scale(+x_min) : -Infinity;\n const x_max_px = isFinite(x_max) ? x_scale(+x_max) : Infinity;\n // For y px, we flip the data min/max b/c in SVG coord system +y is down: smaller data y = larger px y\n const y_min_px = isFinite(y_max) ? y_scale(+y_max) : -Infinity;\n const y_max_px = isFinite(y_min) ? y_scale(+y_min) : Infinity;\n track_data = coalesce_scatter_points(track_data, x_min_px, x_max_px, x_gap, y_min_px, y_max_px, y_gap);\n }\n\n if (this.layout.label) {\n let label_data;\n const filters = data_layer.layout.label.filters || [];\n if (!filters.length) {\n label_data = track_data;\n } else {\n const func = this.filter.bind(this, filters);\n label_data = track_data.filter(func);\n }\n\n // Render label groups\n this._label_groups = this.svg.group\n .selectAll(`g.lz-data_layer-${this.layout.type}-label`)\n .data(label_data, (d) => `${d[this.layout.id_field]}_label`);\n\n const style_class = `lz-data_layer-${this.layout.type}-label`;\n const groups_enter = this._label_groups.enter()\n .append('g')\n .attr('class', style_class);\n\n if (this._label_texts) {\n this._label_texts.remove();\n }\n\n this._label_texts = this._label_groups.merge(groups_enter)\n .append('text')\n .text((d) => parseFields(data_layer.layout.label.text || '', d, this.getElementAnnotation(d)))\n .attr('x', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + data_layer.layout.label.spacing;\n })\n .attr('y', (d) => d[ycs])\n .attr('text-anchor', 'start')\n .call(applyStyles, data_layer.layout.label.style || {});\n\n // Render label lines\n if (data_layer.layout.label.lines) {\n if (this._label_lines) {\n this._label_lines.remove();\n }\n this._label_lines = this._label_groups.merge(groups_enter)\n .append('line')\n .attr('x1', (d) => d[xcs])\n .attr('y1', (d) => d[ycs])\n .attr('x2', (d) => {\n return d[xcs]\n + Math.sqrt(data_layer.resolveScalableParameter(data_layer.layout.point_size, d))\n + (data_layer.layout.label.spacing / 2);\n })\n .attr('y2', (d) => d[ycs])\n .call(applyStyles, data_layer.layout.label.lines.style || {});\n }\n // Remove labels when they're no longer in the filtered data set\n this._label_groups.exit()\n .remove();\n } else {\n // If the layout definition has changed (& no longer specifies labels), strip any previously rendered\n if (this._label_texts) {\n this._label_texts.remove();\n }\n if (this._label_lines) {\n this._label_lines.remove();\n }\n if (this._label_groups) {\n this._label_groups.remove();\n }\n }\n\n // Generate main scatter data elements\n const selection = this.svg.group\n .selectAll(`path.lz-data_layer-${this.layout.type}`)\n .data(track_data, (d) => d[this.layout.id_field]);\n\n // Create elements, apply class, ID, and initial position\n // Generate new values (or functions for them) for position, color, size, and shape\n const transform = (d) => `translate(${d[xcs]}, ${d[ycs]})`;\n\n const shape = d3.symbol()\n .size((d, i) => this.resolveScalableParameter(this.layout.point_size, d, i))\n .type((d, i) => nameToSymbol(this.resolveScalableParameter(this.layout.point_shape, d, i)));\n\n const style_class = `lz-data_layer-${this.layout.type}`;\n selection.enter()\n .append('path')\n .attr('class', style_class)\n .attr('id', (d) => this.getElementId(d))\n .merge(selection)\n .attr('transform', transform)\n .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))\n .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i))\n .attr('d', shape);\n\n // Remove old elements as needed\n selection.exit()\n .remove();\n\n // Apply method to keep labels from overlapping each other\n if (this.layout.label) {\n this.flip_labels();\n this._label_iterations = 0;\n this.separate_labels();\n }\n\n // Apply default event emitters & mouse behaviors. Apply to the container, not per element,\n // to reduce number of event listeners. These events will apply to both scatter points and labels.\n this.svg.group\n .on('click.event_emitter', () => {\n // D3 doesn't natively support bubbling very well; we need to find the data for the bubbled event\n const item_data = d3.select(d3.event.target).datum();\n this.parent.emit('element_clicked', item_data, true);\n })\n .call(this.applyBehaviors.bind(this));\n }\n\n /**\n * A new LD reference variant has been selected (usually by clicking within a GWAS scatter plot)\n * This event only fires for manually selected variants. It does not fire if the LD reference variant is\n * automatically selected (eg by choosing the most significant hit in the region)\n * @event set_ldrefvar\n * @property {object} data { ldrefvar } The variant identifier of the LD reference variant\n * @see event:any_lz_event\n */\n\n /**\n * Method to set a passed element as the LD reference variant in the plot-level state. Triggers a re-render\n * so that the plot will update with the new LD information.\n * This is useful in tooltips, eg the \"make LD reference\" action link for GWAS scatter plots.\n * @param {object} element The data associated with a particular plot element\n * @fires event:set_ldrefvar\n * @return {Promise}\n */\n makeLDReference(element) {\n let ref = null;\n if (typeof element == 'undefined') {\n throw new Error('makeLDReference requires one argument of any type');\n } else if (typeof element == 'object') {\n if (this.layout.id_field && typeof element[this.layout.id_field] != 'undefined') {\n ref = element[this.layout.id_field].toString();\n } else if (typeof element['id'] != 'undefined') {\n ref = element['id'].toString();\n } else {\n ref = element.toString();\n }\n } else {\n ref = element.toString();\n }\n this.parent.emit('set_ldrefvar', { ldrefvar: ref }, true);\n return this.parent_plot.applyState({ ldrefvar: ref });\n }\n}\n\n/**\n * A scatter plot in which the x-axis represents categories, rather than individual positions.\n * For example, this can be used by PheWAS plots to show related groups. This plot allows the categories and color options to be\n * determined dynamically when data is first loaded.\n * @alias module:LocusZoom_DataLayers~category_scatter\n */\nclass CategoryScatter extends Scatter {\n /**\n * @param {string} layout.x_axis.category_field The datum field to use in auto-generating tick marks, color scheme, and point ordering.\n */\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * In the form {category_name: [min_x, max_x]}\n * @private\n * @member {Object.}\n */\n this._categories = {};\n }\n\n /**\n * This plot layer makes certain assumptions about the data passed in. Transform the raw array of records from\n * the datasource to prepare it for plotting, as follows:\n * 1. The scatter plot assumes that all records are given in sequence (pre-grouped by `category_field`)\n * 2. It assumes that all records have an x coordinate for individual plotting\n * @private\n */\n _prepareData() {\n const xField = this.layout.x_axis.field || 'x';\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n if (!category_field) {\n throw new Error(`Layout for ${this.layout.id} must specify category_field`);\n }\n // Sort the data so that things in the same category are adjacent (case-insensitive by specified field)\n const sourceData = this.data\n .sort((a, b) => {\n const ak = a[category_field];\n const bk = b[category_field];\n const av = (typeof ak === 'string') ? ak.toLowerCase() : ak;\n const bv = (typeof bk === 'string') ? bk.toLowerCase() : bk;\n return (av === bv) ? 0 : (av < bv ? -1 : 1);\n });\n sourceData.forEach((d, i) => {\n // Implementation detail: Scatter plot requires specifying an x-axis value, and most datasources do not\n // specify plotting positions. If a point is missing this field, fill in a synthetic value.\n d[xField] = d[xField] || i;\n });\n return sourceData;\n }\n\n /**\n * Identify the unique categories on the plot, and update the layout with an appropriate color scheme.\n * Also identify the min and max x value associated with the category, which will be used to generate ticks\n * @private\n * @returns {Object.} Series of entries used to build category name ticks {category_name: [min_x, max_x]}\n */\n _generateCategoryBounds() {\n // TODO: API may return null values in category_field; should we add placeholder category label?\n // The (namespaced) field from `this.data` that will be used to assign datapoints to a given category & color\n const category_field = this.layout.x_axis.category_field;\n const xField = this.layout.x_axis.field || 'x';\n const uniqueCategories = {};\n this.data.forEach((item) => {\n const category = item[category_field];\n const x = item[xField];\n const bounds = uniqueCategories[category] || [x, x];\n uniqueCategories[category] = [Math.min(bounds[0], x), Math.max(bounds[1], x)];\n });\n\n const categoryNames = Object.keys(uniqueCategories);\n this._setDynamicColorScheme(categoryNames);\n\n return uniqueCategories;\n }\n\n /**\n * This layer relies on defining its own category-based color scheme. Find the correct color config object to\n * be modified.\n * @param [from_source]\n * @returns {Object} A mutable reference to the layout configuration object\n * @private\n */\n _getColorScale(from_source) {\n from_source = from_source || this.layout;\n // If the layout does not use a supported coloring scheme, or is already complete, this method should do nothing\n\n // For legacy reasons, layouts can specify color as an object (only one way to set color), as opposed to the\n // preferred mechanism of array (multiple coloring options)\n let color_params = from_source.color || []; // Object or scalar, no other options allowed\n if (Array.isArray(color_params)) {\n color_params = color_params.find((item) => item.scale_function === 'categorical_bin');\n }\n if (!color_params || color_params.scale_function !== 'categorical_bin') {\n throw new Error('This layer requires that color options be provided as a `categorical_bin`');\n }\n return color_params;\n }\n\n /**\n * Automatically define a color scheme for the layer based on data returned from the server.\n * If part of the color scheme has been specified, it will fill in remaining missing information.\n *\n * There are three scenarios:\n * 1. The layout does not specify either category names or (color) values. Dynamically build both based on\n * the data and update the layout.\n * 2. The layout specifies colors, but not categories. Use that exact color information provided, and dynamically\n * determine what categories are present in the data. (cycle through the available colors, reusing if there\n * are a lot of categories)\n * 3. The layout specifies exactly what colors and categories to use (and they match the data!). This is useful to\n * specify an explicit mapping between color scheme and category names, when you want to be sure that the\n * plot matches a standard color scheme.\n * (If the layout specifies categories that do not match the data, the user specified categories will be ignored)\n *\n * This method will only act if the layout defines a `categorical_bin` scale function for coloring. It may be\n * overridden in a subclass to suit other types of coloring methods.\n *\n * @param {String[]} categoryNames\n * @private\n */\n _setDynamicColorScheme(categoryNames) {\n const colorParams = this._getColorScale(this.layout).parameters;\n const baseParams = this._getColorScale(this._base_layout).parameters;\n\n if (baseParams.categories.length && baseParams.values.length) {\n // If there are preset category/color combos, make sure that they apply to the actual dataset\n const parameters_categories_hash = {};\n baseParams.categories.forEach((category) => {\n parameters_categories_hash[category] = 1;\n });\n if (categoryNames.every((name) => Object.prototype.hasOwnProperty.call(parameters_categories_hash, name))) {\n // The layout doesn't have to specify categories in order, but make sure they are all there\n colorParams.categories = baseParams.categories;\n } else {\n colorParams.categories = categoryNames;\n }\n } else {\n colorParams.categories = categoryNames;\n }\n // Prefer user-specified colors if provided. Make sure that there are enough colors for all the categories.\n let colors;\n if (baseParams.values.length) {\n colors = baseParams.values;\n } else {\n // Originally from d3v3 category20\n colors = ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];\n }\n while (colors.length < categoryNames.length) {\n colors = colors.concat(colors);\n }\n colors = colors.slice(0, categoryNames.length); // List of hex values, should be of same length as categories array\n colorParams.values = colors;\n }\n\n /**\n *\n * @param dimension\n * @param {Object} [config] Parameters that customize how ticks are calculated (not style)\n * @param {('left'|'center'|'right')} [config.position='left'] Align ticks with the center or edge of category\n * @returns {Array}\n */\n getTicks(dimension, config) { // Overrides parent method\n if (!['x', 'y1', 'y2'].includes(dimension)) {\n throw new Error('Invalid dimension identifier');\n }\n const position = config.position || 'left';\n if (!['left', 'center', 'right'].includes(position)) {\n throw new Error('Invalid tick position');\n }\n\n const categoryBounds = this._categories;\n if (!categoryBounds || !Object.keys(categoryBounds).length) {\n return [];\n }\n\n if (dimension === 'y') {\n return [];\n }\n\n if (dimension === 'x') {\n // If colors have been defined by this layer, use them to make tick colors match scatterplot point colors\n const colors = this._getColorScale(this.layout);\n const knownCategories = colors.parameters.categories || [];\n const knownColors = colors.parameters.values || [];\n\n return Object.keys(categoryBounds).map((category, index) => {\n const bounds = categoryBounds[category];\n let xPos;\n\n switch (position) {\n case 'left':\n xPos = bounds[0];\n break;\n case 'center':\n // Center tick under one or many elements as appropriate\n // eslint-disable-next-line no-case-declarations\n const diff = bounds[1] - bounds[0];\n xPos = bounds[0] + (diff !== 0 ? diff : bounds[0]) / 2;\n break;\n case 'right':\n xPos = bounds[1];\n break;\n }\n return {\n x: xPos,\n text: category,\n style: {\n 'fill': knownColors[knownCategories.indexOf(category)] || '#000000',\n },\n };\n });\n }\n }\n\n applyCustomDataMethods() {\n this.data = this._prepareData();\n this._categories = this._generateCategoryBounds();\n return this;\n }\n}\n\n\nexport { Scatter as scatter, CategoryScatter as category_scatter };\n","/**\n * Helper functions targeted at rendering operations\n * @module\n * @private\n*/\n\n\n/**\n * A very simple function aimed at scatter plots: attempts to coalesce \"low-significance\" SNPs that are too close to\n * visually distinguish, thus creating a dataset with fewer points that can be rendered more quickly.\n *\n * This depends on the strong and explicit assumption that points are ordered (typically in x position), so that\n * nearby points can be grouped by iterating over the data in sequence.\n *\n * @param {Object[]} data Plot data, annotated with calculated `xc` and `yc` symbols for x and y coordinates (in px).\n * @param {Number} x_min The smallest x value of an \"insignificant region\" rectangle\n * @param {Number} x_max The largest x value of an \"insignificant region\" rectangle\n * @param {Number} x_gap Max px distance, in x direction, from the first point in a set, to qualify for grouping\n * @param {Number} y_min The smallest y value of an \"insignificant region\" rectangle\n * @param {Number} y_max The largest y value of an \"insignificant region\" rectangle\n * @param {Number} y_gap Max px distance, in y direction, from the first point in a set, to qualify for grouping\n * @return {Object[]} The simplified dataset with fewer points\n */\nfunction coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap) {\n let final_data = [];\n\n const xcs = Symbol.for('lzX');\n const ycs = Symbol.for('lzY');\n\n let x_start = null;\n let y_start = null;\n let current_group = [];\n\n function _combine () {\n if (current_group.length) {\n // If there are points near each other, return the middle item to represent the group\n // We use a real point (rather than a synthetic average point) to best handle extra fields\n const item = current_group[Math.floor((current_group.length - 1) / 2)];\n final_data.push(item);\n }\n x_start = y_start = null;\n current_group = [];\n }\n\n function _start_run(x, y, item) {\n x_start = x;\n y_start = y;\n current_group.push(item);\n }\n\n data.forEach((item) => {\n const x = item[xcs];\n const y = item[ycs];\n\n const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max);\n if (item.lz_is_match || !in_combine_region) {\n // If an item is marked as interesting in some way, always render it explicitly\n // (and coalesce the preceding points if a run was in progress, to preserve ordering)\n _combine();\n final_data.push(item);\n } else if (x_start === null) {\n // If not tracking a group, start tracking\n _start_run(x, y, item);\n } else {\n // Otherwise, the decision to render the point depends on whether it is close to a run of other\n // insignificant points\n const near_prior = Math.abs(x - x_start) <= x_gap && Math.abs(y - y_start) <= y_gap;\n\n if (near_prior) {\n current_group.push(item);\n } else {\n // \"if in combine region, and not near a prior point, coalesce all prior items, then track this point\n // as part of the next run that could be grouped\"\n _combine();\n _start_run(x, y, item);\n }\n }\n });\n // At the end of the dataset, check whether any runs of adjacent points were in progress, and coalesce if so\n _combine();\n\n return final_data;\n}\n\nexport { coalesce_scatter_points };\n","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as layers from '../components/data_layer';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data rendering types (data layers).\n * @alias module:LocusZoom~DataLayers\n * @type {module:registry/base~ClassRegistry}\n */\nconst registry = new ClassRegistry();\nfor (let [name, type] of Object.entries(layers)) {\n registry.add(name, type);\n}\n\n\nexport default registry;\n","/**\n * Predefined layouts that describe how to draw common types of data, as well as what interactive features to use.\n * Each plot contains multiple panels (rows), and each row can stack several kinds of data in layers\n * (eg scatter plot and line of significance). Layouts provide the building blocks to provide interactive experiences\n * and user-friendly tooltips for common kinds of genetic data.\n *\n * Many of these layouts (like the standard association plot) assume that field names are the same as those provided\n * in the UMich [portaldev API](https://portaldev.sph.umich.edu/docs/api/v1/). Although layouts can be used on many\n * kinds of data, it is often less work to write an adapter that uses the same field names, rather than to modify\n * every single reference to a field anywhere in the layout.\n *\n * See the Layouts Tutorial for details on how to customize nested layouts.\n *\n * @module LocusZoom_Layouts\n */\n\nimport version from '../version';\nimport {deepCopy, merge} from '../helpers/layouts';\n\nconst LZ_SIG_THRESHOLD_LOGP = 7.301; // -log10(.05/1e6)\n\n/*\n * Tooltip Layouts\n */\nconst standard_association_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `{{assoc:variant|htmlescape}}
                                                                                                                                                                                                                                                                                \n P Value: {{assoc:log_pvalue|logtoscinotation|htmlescape}}
                                                                                                                                                                                                                                                                                \n Ref. Allele: {{assoc:ref_allele|htmlescape}}
                                                                                                                                                                                                                                                                                \n {{#if lz_is_ld_refvar}}LD Reference Variant{{#else}}\n Make LD Reference{{/if}}
                                                                                                                                                                                                                                                                                `,\n};\n\nconst standard_association_tooltip_with_label = function() {\n // Add a special \"toggle label\" button to the base tooltip. This must be used in tandem with a custom layout\n // directive (label.filters should check a boolean annotation field called \"lz_show_label\").\n const base = deepCopy(standard_association_tooltip);\n base.html += `{{#if lz_show_label}}Hide{{#else}}Show{{/if}} label`;\n return base;\n}();\n\nconst standard_genes_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '

                                                                                                                                                                                                                                                                                {{gene_name|htmlescape}}

                                                                                                                                                                                                                                                                                '\n + 'Gene ID: {{gene_id|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + 'Transcript ID: {{transcript_id|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + '{{#if pLI}}'\n + ''\n + ''\n + ''\n + ''\n + '
                                                                                                                                                                                                                                                                                ConstraintExpected variantsObserved variantsConst. Metric
                                                                                                                                                                                                                                                                                Synonymous{{exp_syn}}{{obs_syn}}z = {{syn_z}}
                                                                                                                                                                                                                                                                                o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})
                                                                                                                                                                                                                                                                                Missense{{exp_mis}}{{obs_mis}}z = {{mis_z}}
                                                                                                                                                                                                                                                                                o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})
                                                                                                                                                                                                                                                                                pLoF{{exp_lof}}{{obs_lof}}pLI = {{pLI}}
                                                                                                                                                                                                                                                                                o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})

                                                                                                                                                                                                                                                                                {{/if}}'\n + 'More data on gnomAD',\n};\n\nconst catalog_variant_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: '{{catalog:variant|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + 'Catalog entries: {{n_catalog_matches|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + 'Top Trait: {{catalog:trait|htmlescape}}
                                                                                                                                                                                                                                                                                '\n + 'Top P Value: {{catalog:log_pvalue|logtoscinotation}}
                                                                                                                                                                                                                                                                                '\n // User note: if a different catalog is used, the tooltip will need to be replaced with a different link URL\n + 'More: GWAS catalog / dbSNP',\n};\n\nconst coaccessibility_tooltip = {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n // TODO: Is there a more generic terminology? (eg not every technique is in terms of cis-regulatory element)\n html: 'Regulatory element
                                                                                                                                                                                                                                                                                ' +\n '{{access:start1|htmlescape}}-{{access:end1|htmlescape}}
                                                                                                                                                                                                                                                                                ' +\n 'Promoter
                                                                                                                                                                                                                                                                                ' +\n '{{access:start2|htmlescape}}-{{access:end2|htmlescape}}
                                                                                                                                                                                                                                                                                ' +\n '{{#if access:target}}Target: {{access:target|htmlescape}}
                                                                                                                                                                                                                                                                                {{/if}}' +\n 'Score: {{access:score|htmlescape}}',\n};\n\n/*\n * Data Layer Layouts: represent specific information given provided data.\n */\n\n/**\n * A horizontal line of GWAS significance at the standard threshold of p=5e-8\n * @name significance\n * @type data_layer\n */\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n tag: 'significance',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\n/**\n * A simple curve representing the genetic recombination rate, drawn from the UM API\n * @name recomb_rate\n * @type data_layer\n */\nconst recomb_rate_layer = {\n id: 'recombrate',\n namespace: { 'recomb': 'recomb' },\n data_operations: [\n { type: 'fetch', from: ['recomb'] },\n ],\n type: 'line',\n tag: 'recombination',\n z_index: 1,\n style: {\n 'stroke': '#0000FF',\n 'stroke-width': '1.5px',\n },\n x_axis: {\n field: 'recomb:position',\n },\n y_axis: {\n axis: 2,\n field: 'recomb:recomb_rate',\n floor: 0,\n ceiling: 100,\n },\n};\n\n/**\n * A scatter plot of GWAS association summary statistics, with preset field names matching the UM portaldev api\n * @name association_pvalues\n * @type data_layer\n */\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n data_operations: [\n {\n type: 'fetch',\n from: ['assoc', 'ld(assoc)'],\n },\n {\n type: 'left_match',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'ld'],\n params: ['assoc:position', 'ld:position2'], // FIXME: old LZ used position, because it was less sensitive to format. We'd like to match assoc:variant = ld:variant2, but not every assoc source provides variant data in the way we need. This would need to be fixed via special formatting adjustment later.\n },\n ],\n id: 'associationpvalues',\n type: 'scatter',\n tag: 'association',\n id_field: 'assoc:variant',\n coalesce: {\n active: true,\n },\n point_shape: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 'diamond',\n },\n },\n {\n // Not every dataset will provide these params\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'assoc:beta',\n stderr_beta_field: 'assoc:se',\n },\n },\n 'circle',\n ],\n point_size: {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: 80,\n else: 40,\n },\n },\n color: [\n {\n scale_function: 'if',\n field: 'lz_is_ld_refvar',\n parameters: {\n field_value: true,\n then: '#9632b8',\n },\n },\n {\n scale_function: 'numerical_bin',\n field: 'ld:correlation',\n parameters: {\n breaks: [0, 0.2, 0.4, 0.6, 0.8],\n // Derived from Google \"Turbo\" colormap, breakpoints [0.05, 0.25, 0.45, 0.65, 0.85]\n values: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n },\n },\n '#AAAAAA',\n ],\n legend: [\n { label: 'LD (r²)', label_size: 14 }, // We're omitting the refvar symbol for now, but can show it with // shape: 'diamond', color: '#9632b8'\n {\n shape: 'ribbon',\n orientation: 'vertical',\n width: 10,\n height: 15,\n color_stops: ['rgb(70, 54, 153)', 'rgb(38, 188, 225)', 'rgb(110, 254, 104)', 'rgb(248, 195, 42)', 'rgb(219, 61, 17)'],\n tick_labels: [0, 0.2, 0.4, 0.6, 0.8, 1.0],\n },\n ],\n label: null,\n z_index: 2,\n x_axis: {\n field: 'assoc:position',\n },\n y_axis: {\n axis: 1,\n field: 'assoc:log_pvalue',\n floor: 0,\n upper_buffer: 0.10,\n min_extent: [0, 10],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_association_tooltip),\n};\n\n/**\n * An arc track that shows arcs representing chromatic coaccessibility\n * @name coaccessibility\n * @type data_layer\n */\nconst coaccessibility_layer = {\n id: 'coaccessibility',\n type: 'arcs',\n tag: 'coaccessibility',\n namespace: { 'access': 'access' },\n data_operations: [\n { type: 'fetch', from: ['access'] },\n ],\n match: { send: 'access:target', receive: 'access:target' },\n // Note: in the datasets this was tested with, these fields together defined a unique loop. Other datasets might work differently and need a different ID.\n id_field: '{{access:start1}}_{{access:end1}}_{{access:start2}}_{{access:end2}}_{{access:score}}_{{access:target}}',\n filters: [\n { field: 'access:score', operator: '!=', value: null },\n ],\n color: [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n {\n scale_function: 'ordinal_cycle',\n parameters: {\n values: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], // Drawn from d3v3 \"category20\"\n },\n },\n ],\n x_axis: {\n field1: 'access:start1',\n field2: 'access:start2',\n },\n y_axis: {\n axis: 1,\n field: 'access:score',\n upper_buffer: 0.1,\n min_extent: [0, 1],\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(coaccessibility_tooltip),\n};\n\n/**\n * A scatter plot of GWAS summary statistics, with additional tooltip fields showing GWAS catalog annotations\n * @name association_pvalues_catalog\n * @type data_layer\n */\nconst association_pvalues_catalog_layer = function () {\n // Slightly modify an existing layout\n let base = deepCopy(association_pvalues_layer);\n base = merge({ id: 'associationpvaluescatalog', fill_opacity: 0.7 }, base);\n\n base.data_operations.push({\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_catalog',\n requires: ['assoc_plus_ld', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n });\n\n base.tooltip.html += '{{#if catalog:rsid}}
                                                                                                                                                                                                                                                                                See hits in GWAS catalog{{/if}}';\n base.namespace.catalog = 'catalog';\n return base;\n}();\n\n\n/**\n * A scatter plot of PheWAS pvalues, with preset field names matching the UM Portaldev API\n * @name phewas_pvalues\n * @type data_layer\n */\nconst phewas_pvalues_layer = {\n id: 'phewaspvalues',\n type: 'category_scatter',\n tag: 'phewas',\n namespace: { 'phewas': 'phewas' },\n data_operations: [\n { type: 'fetch', from: ['phewas'] },\n ],\n point_shape: [\n {\n scale_function: 'effect_direction',\n parameters: {\n '+': 'triangle',\n '-': 'triangledown',\n // The scale function receives the entire datum object, so it needs to be told where to find individual fields\n beta_field: 'phewas:beta',\n stderr_beta_field: 'phewas:se',\n },\n },\n 'circle',\n ],\n point_size: 70,\n tooltip_positioning: 'vertical',\n id_field: '{{phewas:trait_group}}_{{phewas:trait_label}}',\n x_axis: {\n field: 'lz_auto_x', // Automatically added by the category_scatter layer\n category_field: 'phewas:trait_group',\n lower_buffer: 0.025,\n upper_buffer: 0.025,\n },\n y_axis: {\n axis: 1,\n field: 'phewas:log_pvalue',\n floor: 0,\n upper_buffer: 0.15,\n },\n color: [{\n field: 'phewas:trait_group',\n scale_function: 'categorical_bin',\n parameters: {\n categories: [],\n values: [],\n null_value: '#B8B8B8',\n },\n }],\n fill_opacity: 0.7,\n tooltip: {\n closable: true,\n show: { or: ['highlighted', 'selected'] },\n hide: { and: ['unhighlighted', 'unselected'] },\n html: `Trait: {{phewas:trait_label|htmlescape}}
                                                                                                                                                                                                                                                                                \nTrait Category: {{phewas:trait_group|htmlescape}}
                                                                                                                                                                                                                                                                                \nP-value: {{phewas:log_pvalue|logtoscinotation|htmlescape}}\n{{#if phewas:beta|is_numeric}}
                                                                                                                                                                                                                                                                                β: {{phewas:beta|scinotation|htmlescape}}
                                                                                                                                                                                                                                                                                {{/if}}\n{{#if phewas:se|is_numeric}}SE (β): {{phewas:se|scinotation|htmlescape}}{{/if}}`,\n },\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n label: {\n text: '{{phewas:trait_label}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n {\n field: 'phewas:log_pvalue',\n operator: '>=',\n value: 20,\n },\n ],\n style: {\n 'font-size': '14px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n};\n\n/**\n * Shows genes in the specified region, with names and formats drawn from the UM Portaldev API and GENCODE datasource\n * @type data_layer\n */\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n data_operations: [\n {\n type: 'fetch',\n from: ['gene', 'constraint(gene)'],\n },\n {\n name: 'gene_constraint',\n type: 'genes_to_gnomad_constraint',\n requires: ['gene', 'constraint'],\n },\n ],\n id: 'genes',\n type: 'genes',\n tag: 'genes',\n id_field: 'gene_id',\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(standard_genes_tooltip),\n};\n\n/**\n * A genes data layer that uses filters to limit what information is shown by default. This layer hides a curated\n * list of GENCODE gene_types that are of less interest to most analysts.\n * Often used in tandem with a panel-level toolbar \"show all\" button so that the user can toggle to a full view.\n * @name genes_filtered\n * @type data_layer\n */\nconst genes_layer_filtered = merge({\n filters: [\n {\n field: 'gene_type',\n operator: 'in',\n // A manually curated subset of Gencode biotypes, based on user suggestions\n // See full list: https://www.gencodegenes.org/human/stats.html\n // This is approximately intended to cover elements of generally known function, and exclude things\n // like pseudogenes.\n value: [\n 'protein_coding',\n 'IG_C_gene', 'IG_D_gene', 'IG_J_gene', 'IG_V_gene',\n 'TR_C_gene', 'TR_D_gene', 'TR_J_gene', 'TR_V_gene',\n 'rRNA',\n 'Mt_rRNA', 'Mt_tRNA',\n ],\n },\n ],\n}, deepCopy(genes_layer));\n\n/**\n * An annotation / rug track that shows tick marks for each position in which a variant is present in the provided\n * association data, *and* has a significant claim in the EBI GWAS catalog.\n * @type data_layer\n */\nconst annotation_catalog_layer = {\n // Identify GWAS hits that are present in the GWAS catalog\n namespace: { 'assoc': 'assoc', 'catalog': 'catalog' },\n data_operations: [\n {\n type: 'fetch', from: ['assoc', 'catalog'],\n },\n {\n type: 'assoc_to_gwas_catalog',\n name: 'assoc_plus_ld',\n requires: ['assoc', 'catalog'],\n params: ['assoc:position', 'catalog:pos', 'catalog:log_pvalue'],\n },\n ],\n id: 'annotation_catalog',\n type: 'annotation_track',\n tag: 'gwascatalog',\n id_field: 'assoc:variant',\n x_axis: {\n field: 'assoc:position',\n },\n color: '#0000CC',\n filters: [\n // Specify which points to show on the track. Any selection must satisfy ALL filters\n { field: 'catalog:rsid', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n ],\n behaviors: {\n onmouseover: [\n { action: 'set', status: 'highlighted' },\n ],\n onmouseout: [\n { action: 'unset', status: 'highlighted' },\n ],\n onclick: [\n { action: 'toggle', status: 'selected', exclusive: true },\n ],\n },\n tooltip: deepCopy(catalog_variant_tooltip),\n tooltip_positioning: 'top',\n};\n\n/*\n * Individual toolbar buttons\n */\n\n/**\n * A dropdown menu that can be used to control the LD population used with the LDServer Adapter. Population\n * names are provided for the 1000G dataset that is used by the offical UM LD Server.\n * @name ldlz2_pop_selector\n * @type toolbar_widgets\n */\nconst ldlz2_pop_selector_menu = {\n // **Note**: this widget is aimed at the LDServer datasource, and the UM 1000G LDServer\n type: 'set_state',\n tag: 'ld_population',\n position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\n custom_event_name: 'widget_set_ldpop',\n state_field: 'ld_pop',\n // This list below is hardcoded to work with the UMich LDServer, default 1000G populations\n // It can be customized to work with other LD servers that specify population differently\n // https://portaldev.sph.umich.edu/ld/genome_builds/GRCh37/references/1000G/populations\n options: [\n { display_name: 'ALL (default)', value: 'ALL' },\n { display_name: 'AFR', value: 'AFR' },\n { display_name: 'AMR', value: 'AMR' },\n { display_name: 'EAS', value: 'EAS' },\n { display_name: 'EUR', value: 'EUR' },\n { display_name: 'SAS', value: 'SAS' },\n ],\n};\n\n/**\n * A dropdown menu that selects which types of genes to show in the plot. The provided options are curated sets of\n * interesting gene types based on the GENCODE dataset.\n * @type toolbar_widgets\n */\nconst gene_selector_menu = {\n type: 'display_options',\n tag: 'gene_filter',\n custom_event_name: 'widget_gene_filter_choice',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Filter...',\n button_title: 'Choose which genes to show',\n layer_name: 'genes',\n default_config_display_name: 'Coding genes & rRNA',\n options: [\n {\n display_name: 'All features',\n display: {\n filters: null,\n },\n },\n ],\n};\n\n/*\n * Toolbar Layouts: Collections of toolbar buttons etc\n */\n\n/**\n * Basic options to remove and reorder panels\n * @name standard_panel\n * @type toolbar\n */\nconst standard_panel_toolbar = {\n widgets: [\n {\n type: 'remove_panel',\n position: 'right',\n color: 'red',\n group_position: 'end',\n },\n {\n type: 'move_panel_up',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'move_panel_down',\n position: 'right',\n group_position: 'start',\n style: { 'margin-left': '0.75em' },\n },\n ],\n};\n\n/**\n * A simple plot toolbar with buttons to download as image\n * @name standard_plot\n * @type toolbar\n */\nconst standard_plot_toolbar = {\n // Suitable for most any type of plot drawn with LZ. Title and download buttons.\n widgets: [\n {\n type: 'title',\n title: 'LocusZoom',\n subtitle: `v${version}`,\n position: 'left',\n },\n {\n type: 'download',\n position: 'right',\n group_position: 'end',\n },\n {\n type: 'download_png',\n position: 'right',\n group_position: 'start',\n },\n ],\n};\n\n/**\n * A plot toolbar that adds a button for controlling LD population. This is useful for plots intended to show\n * GWAS summary stats, which is one of the most common usages of LocusZoom.\n * @type toolbar\n */\nconst standard_association_toolbar = function () {\n // Suitable for association plots (adds a button for LD data)\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(deepCopy(ldlz2_pop_selector_menu));\n return base;\n}();\n\n/**\n * A basic plot toolbar with buttons to scroll sideways or zoom in. Useful for all region-based plots.\n * @name region_nav_plot\n * @type toolbar\n */\nconst region_nav_plot_toolbar = function () {\n // Generic region nav buttons\n const base = deepCopy(standard_plot_toolbar);\n base.widgets.push(\n {\n type: 'shift_region',\n step: 500000,\n button_html: '>>',\n position: 'right',\n group_position: 'end',\n }, {\n type: 'shift_region',\n step: 50000,\n button_html: '>',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: 0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'zoom_region',\n step: -0.2,\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -50000,\n button_html: '<',\n position: 'right',\n group_position: 'middle',\n },\n {\n type: 'shift_region',\n step: -500000,\n button_html: '<<',\n position: 'right',\n group_position: 'start',\n },\n );\n return base;\n}();\n\n/*\n * Panel Layouts\n */\n\n\n/**\n * A panel that describes the most common kind of LocusZoom plot, with line of GWAS significance, recombination rate,\n * and a scatter plot superimposed.\n * @name association\n * @type panel\n */\nconst association_panel = {\n id: 'association',\n tag: 'association',\n min_height: 200,\n height: 300,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push({\n type: 'toggle_legend',\n position: 'right',\n });\n return base;\n })(),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n y2: {\n label: 'Recombination Rate (cM/Mb)',\n label_offset: 46,\n },\n },\n legend: {\n orientation: 'vertical',\n origin: { x: 75, y: 40 },\n hidden: true,\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n drag_y2_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_layer),\n ],\n};\n\n/**\n * A panel showing chromatin coaccessibility arcs with some common display options\n * @type panel\n */\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n tag: 'coaccessibility',\n min_height: 150,\n height: 180,\n margin: { top: 35, right: 55, bottom: 40, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n y1: {\n label: 'Score',\n label_offset: 40,\n render: false, // We are mainly concerned with the relative magnitudes: hide y axis to avoid clutter.\n },\n },\n interaction: {\n drag_background_to_pan: true,\n drag_x_ticks_to_scale: true,\n drag_y1_ticks_to_scale: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(coaccessibility_layer),\n ],\n};\n\n/**\n * A panel showing GWAS summary statistics, plus annotations for connecting it to the EBI GWAS catalog\n * @type panel\n */\nconst association_catalog_panel = function () {\n let base = deepCopy(association_panel);\n base = merge({\n id: 'associationcatalog',\n }, base);\n\n base.toolbar.widgets.push({\n type: 'display_options',\n position: 'right',\n color: 'blue',\n // Below: special config specific to this widget\n button_html: 'Display options...',\n button_title: 'Control how plot items are displayed',\n\n layer_name: 'associationpvaluescatalog',\n default_config_display_name: 'No catalog labels (default)', // display name for the default plot color option (allow user to revert to plot defaults)\n\n options: [\n {\n // First dropdown menu item\n display_name: 'Label catalog traits', // Human readable representation of field name\n display: { // Specify layout directives that control display of the plot for this option\n label: {\n text: '{{catalog:trait}}',\n spacing: 6,\n lines: {\n style: {\n 'stroke-width': '2px',\n 'stroke': '#333333',\n 'stroke-dasharray': '2px 2px',\n },\n },\n filters: [\n // Only label points if they are significant for some trait in the catalog, AND in high LD\n // with the top hit of interest\n { field: 'catalog:trait', operator: '!=', value: null },\n { field: 'catalog:log_pvalue', operator: '>', value: LZ_SIG_THRESHOLD_LOGP },\n { field: 'ld:correlation', operator: '>', value: 0.4 },\n ],\n style: {\n 'font-size': '12px',\n 'font-weight': 'bold',\n 'fill': '#333333',\n },\n },\n },\n },\n ],\n });\n base.data_layers = [\n deepCopy(significance_layer),\n deepCopy(recomb_rate_layer),\n deepCopy(association_pvalues_catalog_layer),\n ];\n return base;\n}();\n\n/**\n * A panel showing genes in the specified region. This panel lets the user choose which genes are shown.\n * @type panel\n */\nconst genes_panel = {\n id: 'genes',\n tag: 'genes',\n min_height: 150,\n height: 225,\n margin: { top: 20, right: 55, bottom: 20, left: 70 },\n axes: {},\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n toolbar: (function () {\n const base = deepCopy(standard_panel_toolbar);\n base.widgets.push(\n {\n type: 'resize_to_data',\n position: 'right',\n button_html: 'Resize',\n },\n deepCopy(gene_selector_menu),\n );\n return base;\n })(),\n data_layers: [\n deepCopy(genes_layer_filtered),\n ],\n};\n\n/**\n * A panel that displays PheWAS scatter plots and automatically generates a color scheme\n * @type panel\n */\nconst phewas_panel = {\n id: 'phewas',\n tag: 'phewas',\n min_height: 300,\n height: 300,\n margin: { top: 20, right: 55, bottom: 120, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n axes: {\n x: {\n ticks: { // Object based config (shared defaults; allow layers to specify ticks)\n style: {\n 'font-weight': 'bold',\n 'font-size': '11px',\n 'text-anchor': 'start',\n },\n transform: 'rotate(50)',\n position: 'left', // Special param recognized by `category_scatter` layers\n },\n },\n y1: {\n label: '-log10 p-value',\n label_offset: 50,\n },\n },\n data_layers: [\n deepCopy(significance_layer),\n deepCopy(phewas_pvalues_layer),\n ],\n};\n\n/**\n * A panel that shows a simple annotation track connecting GWAS results\n * @name annotation_catalog\n * @type panel\n */\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n tag: 'gwascatalog',\n min_height: 50,\n height: 50,\n margin: { top: 25, right: 55, bottom: 10, left: 70 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\n axes: {\n x: { extent: 'state', render: false },\n },\n interaction: {\n drag_background_to_pan: true,\n scroll_to_zoom: true,\n x_linked: true,\n },\n data_layers: [\n deepCopy(annotation_catalog_layer),\n ],\n};\n\n/*\n * Plot Layouts\n */\n\n/**\n * Describes how to fetch and draw each part of the most common LocusZoom plot (with field names that reference the portaldev API)\n * @name standard_association\n * @type plot\n */\nconst standard_association_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n deepCopy(association_panel),\n deepCopy(genes_panel),\n ],\n};\n\n/**\n * A modified version of the standard LocusZoom plot, which adds a track that shows which SNPs in the plot also have claims in the EBI GWAS catalog.\n * @name association_catalog\n * @type plot\n */\nconst association_catalog_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: standard_association_toolbar,\n panels: [\n annotation_catalog_panel,\n association_catalog_panel,\n genes_panel,\n ],\n};\n\n/**\n * A PheWAS scatter plot with an additional track showing nearby genes, to put the region in biological context.\n * @name standard_phewas\n * @type plot\n */\nconst standard_phewas_plot = {\n width: 800,\n responsive_resize: true,\n toolbar: standard_plot_toolbar,\n panels: [\n deepCopy(phewas_panel),\n merge({\n height: 300,\n margin: { bottom: 40 },\n axes: {\n x: {\n label: 'Chromosome {{chr}} (Mb)',\n label_offset: 38,\n tick_format: 'region',\n extent: 'state',\n },\n },\n }, deepCopy(genes_panel)),\n ],\n mouse_guide: false,\n};\n\n/**\n * Show chromatin coaccessibility arcs, with additional features that connect these arcs to nearby genes to show regulatory interactions.\n * @name coaccessibility\n * @type plot\n */\nconst coaccessibility_plot = {\n state: {},\n width: 800,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n deepCopy(coaccessibility_panel),\n function () {\n // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name\n // This is a companion to the \"match\" directive in the coaccessibility panel\n const base = Object.assign(\n { height: 270 },\n deepCopy(genes_panel),\n );\n const layer = base.data_layers[0];\n layer.match = { send: 'gene_name', receive: 'gene_name' };\n const color_config = [\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#4285f4',\n },\n },\n {\n field: 'lz_is_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: false,\n then: '#EAE6E6',\n },\n },\n '#363696',\n ];\n layer.color = color_config;\n layer.stroke = color_config;\n return base;\n }(),\n ],\n};\n\n\nexport const tooltip = {\n standard_association: standard_association_tooltip,\n standard_association_with_label: standard_association_tooltip_with_label,\n standard_genes: standard_genes_tooltip,\n catalog_variant: catalog_variant_tooltip,\n coaccessibility: coaccessibility_tooltip,\n};\n\nexport const toolbar_widgets = {\n ldlz2_pop_selector: ldlz2_pop_selector_menu,\n gene_selector_menu,\n};\n\nexport const toolbar = {\n standard_panel: standard_panel_toolbar,\n standard_plot: standard_plot_toolbar,\n standard_association: standard_association_toolbar,\n region_nav_plot: region_nav_plot_toolbar,\n};\n\nexport const data_layer = {\n significance: significance_layer,\n recomb_rate: recomb_rate_layer,\n association_pvalues: association_pvalues_layer,\n coaccessibility: coaccessibility_layer,\n association_pvalues_catalog: association_pvalues_catalog_layer,\n phewas_pvalues: phewas_pvalues_layer,\n genes: genes_layer,\n genes_filtered: genes_layer_filtered,\n annotation_catalog: annotation_catalog_layer,\n};\n\nexport const panel = {\n association: association_panel,\n coaccessibility: coaccessibility_panel,\n association_catalog: association_catalog_panel,\n genes: genes_panel,\n phewas: phewas_panel,\n annotation_catalog: annotation_catalog_panel,\n};\n\nexport const plot = {\n standard_association: standard_association_plot,\n association_catalog: association_catalog_plot,\n standard_phewas: standard_phewas_plot,\n coaccessibility: coaccessibility_plot,\n};\n","import {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';\nimport * as layouts from '../layouts';\n\n/**\n * Helper for working with predefined layouts\n *\n * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.\n *\n * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object\n * @public\n * @extends module:registry/base:RegistryBase\n * @inheritDoc\n */\nclass LayoutRegistry extends RegistryBase {\n // Implemented as a \"registry of registries\"- one lookup each for panels, plots, etc...\n get(type, name, overrides = {}) {\n if (!(type && name)) {\n throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');\n }\n // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as\n // applying overrides or applying namespaces.\n let base = super.get(type).get(name);\n\n // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.\n // (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)\n const custom_namespaces = overrides.namespace;\n if (!base.namespace) {\n // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout\n // NOTE: The \"merge namespace\" behavior means that data layers can add new data easily, but this method\n // can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).\n delete overrides.namespace;\n }\n let result = merge(overrides, base);\n\n if (custom_namespaces) {\n result = applyNamespaces(result, custom_namespaces);\n }\n return deepCopy(result);\n }\n\n /**\n * Add a type of layout to the registry\n * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)\n * @param {String} name The name of the layout object to add\n * @param {Object} item The layout object describing parameters\n * @param {boolean} override Whether to replace an existing item by that name\n * @return {*}\n */\n add(type, name, item, override = false) {\n if (!(type && name && item)) {\n throw new Error('To add a layout, type, name, and item must all be specified');\n }\n if (!(typeof item === 'object')) {\n throw new Error('The configuration to be added must be an object');\n }\n\n if (!this.has(type)) {\n super.add(type, new RegistryBase());\n }\n // Ensure that each use of a layout can be modified, by returning a copy is independent\n const copy = deepCopy(item);\n\n // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested\n // from external sources. This is purely a hint, because not every layout is generated through the registry.\n if (type === 'data_layer' && copy.namespace) {\n copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();\n }\n\n return super.get(type).add(name, copy, override);\n }\n\n /**\n * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the\n * layouts for that type of element (\"just predefined panels\").\n * @param {String} [type] The type of layout (eg toolbar, panel, etc)\n * @return {String[]|Object}\n */\n list(type) {\n if (!type) {\n let result = {};\n for (let [type, contents] of this._items) {\n result[type] = contents.list();\n }\n return result;\n }\n return super.get(type).list();\n }\n\n /**\n * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.\n * @static\n * @private\n */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n\n /**\n * Static alias to a helper method. Allows renaming fields\n * @static\n * @private\n */\n renameField() {\n return renameField(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n mutate_attrs() {\n return mutate_attrs(...arguments);\n }\n\n /**\n * Static alias to a helper method. Allows mutating nested layout attributes\n * @static\n * @private\n */\n query_attrs() {\n return query_attrs(...arguments);\n }\n}\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.\n * @alias module:LocusZoom~Layouts\n * @type {LayoutRegistry}\n */\nconst registry = new LayoutRegistry();\n\nfor (let [type, entries] of Object.entries(layouts)) {\n for (let [name, config] of Object.entries(entries)) {\n registry.add(type, name, config);\n }\n}\n\n\nexport default registry;\n\n// Export base class for unit testing\nexport {LayoutRegistry as _LayoutRegistry};\n","/**\n * \"Data operation\" functions, with call signature ({plot_state, data_layer}, [recordsetA, recordsetB...], ...params) => combined_results\n *\n * After data is retrieved from adapters, Data Operations will be run on the resulting data. The most common operation\n * is a \"join\", such as combining association + LD together into a single set of records for plotting. Several join\n * functions (that operate by analogy to SQL) are provided built-in.\n *\n * Other use cases (even if no examples are in the built in code, see unit tests for what is possible):\n * 1. Grouping or filtering records; data operations can consider dynamic properties stored in plot.state.\n * (in the future, adapters may cache more aggressively; if you want to provide your own code for filtering returned data,\n * this is the recommended path to do so)\n * 2. Since the context argument also contains a reference to the data layer instance (and thus the parent panel and plot),\n * a data operation can modify the layout when new data is received, without having to create a custom data layer class. Eg,\n * for datasets where the categories are not known before first render, this could generate automatic x-axis ticks\n * (PheWAS), automatic panel legends or color schemes (BED tracks), etc.\n *\n * Usually, a data operation receives two recordsets (the left and right members of the join, like \"assoc\" and \"ld\").\n * In practice, any number of recordsets can be passed to one join function. There are performance penalties to making too many network\n * requests when rendering a web page, so in practice, joining too many distinct data entities in this fashion is\n * uncommon. (if possible, try to provide your data with fewer adapters/network requests!)\n *\n * In a few cases, the rules of how to combine datasets are very specific to those two types of data. Some,\n * particularly for advanced features, may carry assumptions about field names/ formatting.\n * (example: choosing the best EBI GWAS catalog entry for a variant may look for a field called `log_pvalue` instead of `pvalue`,\n * or it may match two datasets based on a specific way of identifying the variant)\n *\n * @module LocusZoom_DataFunctions\n */\nimport {joins} from '../data/undercomplicate';\n\nimport {RegistryBase} from './base';\n\n/**\n * A plugin registry that allows plots to use both pre-defined and user-provided \"data join\" functions.\n * @alias module:LocusZoom~DataFunctions\n * @type {module:registry/base~RegistryBase}\n */\nconst registry = new RegistryBase();\n\nfunction _wrap_join(handle) {\n // Validate number of arguments and convert call signature from (context, deps, ...params) to (left, right, ...params).\n\n // Many of our join functions are implemented with a different number of arguments than what a datafunction\n // actually receives. (eg, a join function is generic and doesn't care about \"context\" information like plot.state)\n // This wrapper is simple shared code to handle required validation and conversion stuff.\n return (context, deps, ...params) => {\n if (deps.length !== 2) {\n throw new Error('Join functions must receive exactly two recordsets');\n }\n return handle(...deps, ...params);\n };\n}\n\n// Highly specialized join: connect assoc data to GWAS catalog data. This isn't a simple left join, because it tries to\n// pick the most significant claim in the catalog for a variant, rather than joining every possible match.\n// This is specifically intended for sources that obey the ASSOC and CATALOG fields contracts.\nfunction assoc_to_gwas_catalog(assoc_data, catalog_data, assoc_key, catalog_key, catalog_logp_name) {\n if (!assoc_data.length) {\n return assoc_data;\n }\n\n // Prepare the genes catalog: group the data by variant, create simplified dataset with top hit for each\n const catalog_by_variant = joins.groupBy(catalog_data, catalog_key);\n\n const catalog_flat = []; // Store only the top significant claim for each catalog variant entry\n for (let claims of catalog_by_variant.values()) {\n // Find max item within this set of claims, push that to catalog_\n let best = 0;\n let best_variant;\n for (let item of claims) {\n const val = item[catalog_logp_name];\n if ( val >= best) {\n best_variant = item;\n best = val;\n }\n }\n best_variant.n_catalog_matches = claims.length;\n catalog_flat.push(best_variant);\n }\n return joins.left_match(assoc_data, catalog_flat, assoc_key, catalog_key);\n}\n\n// Highly specialized join: connect gnomAD constraint data to genes data. These are two very nonstandard payloads and need a special function to connect them.\nfunction genes_to_gnomad_constraint(genes_data, constraint_data) {\n genes_data.forEach(function(gene) {\n // Find payload keys that match gene names in this response\n const alias = `_${gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_')}`; // aliases are modified gene names\n const constraint = constraint_data[alias] && constraint_data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene\n if (constraint) {\n // Add all fields from constraint data- do not override fields present in the gene source\n Object.keys(constraint).forEach(function (key) {\n let val = constraint[key];\n if (typeof gene[key] === 'undefined') {\n if (typeof val == 'number' && val.toString().includes('.')) {\n val = parseFloat(val.toFixed(2));\n }\n gene[key] = val; // These two sources are both designed to bypass namespacing\n }\n });\n }\n });\n return genes_data;\n}\n\n\n/**\n * Perform a left outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all values in the left recordset, annotated (where applicable) with all keys from matching records in the right recordset\n *\n * @function\n * @name left_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('left_match', _wrap_join(joins.left_match));\n\n/**\n * Perform an inner join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all fields from both recordsets, but only for records where both the left and right keys are defined, and equal. If a record is not in one or both recordsets, it will be excluded from the result.\n *\n * @function\n * @name inner_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('inner_match', _wrap_join(joins.inner_match));\n\n/**\n * Perform a full outer join, based on records where the field values at `left_key` and `right_key` are identical\n *\n * By analogy with SQL, the result will include all records from both the left and right recordsets. If there are matching records, then the relevant items will include fields from both records combined into one.\n *\n * @function\n * @name full_outer_match\n * @param {Object} plot_state\n * @param {Array[]} recordsets\n * @param {String} left_key\n * @params {String} right_key\n */\nregistry.add('full_outer_match', _wrap_join(joins.full_outer_match));\n\n/**\n * A single purpose join function that combines GWAS data with best claim from the EBI GWAS catalog. Essentially this is a left join modified to make further decisions about which records to use.\n *\n * @function\n * @name assoc_to_gwas_catalog\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: assoc records, then catalog records\n * @param {String} assoc_key The name of the key field in association data, eg variant ID\n * @param {String} catalog_key The name of the key field in gwas catalog data, eg variant ID\n * @param {String} catalog_log_p_name The name of the \"log_pvalue\" field in gwas catalog data, used to choose the most significant claim for a given variant\n */\nregistry.add('assoc_to_gwas_catalog', _wrap_join(assoc_to_gwas_catalog));\n\n/**\n * A single purpose join function that combines gene data (UM Portaldev API format) with gene constraint data (gnomAD api format).\n *\n * This acts as a left join that has to perform custom operations to parse two very unusual recordset formats.\n *\n * @function\n * @name genes_to_gnomad_constraint\n * @param {Object} plot_state\n * @param {Array[]} recordsets An array with two items: UM Portaldev API gene records, then gnomAD gene constraint data\n */\nregistry.add('genes_to_gnomad_constraint', _wrap_join(genes_to_gnomad_constraint));\n\nexport default registry;\n","import {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data adapter instances.\n * This is the mechanism by which users tell a plot how to retrieve data for a specific plot: adapters are created\n * through this object rather than instantiating directly.\n *\n * @public\n * @alias module:LocusZoom~DataSources\n * @extends module:registry/base~RegistryBase\n * @inheritDoc\n */\nclass DataSources extends RegistryBase {\n /**\n * @param {RegistryBase} [registry] Primarily used for unit testing. When creating sources by name, specify where to\n * find the registry of known sources.\n */\n constructor(registry) {\n super();\n // This both acts as a registry (of the instantiated sources for this plot), and references a registry\n // (to locate adapter classes by name, when creating from config)\n this._registry = registry || ADAPTERS;\n }\n\n /**\n * For data sources, there is a special behavior of \"create item from config, then add\"\n * @param {String} namespace Uniquely identify this datasource\n * @param {BaseAdapter|Array} item An instantiated datasource, or an array of arguments that can be used to\n * create a known datasource type.\n * @param [override=false] Whether to allow existing sources to be redefined\n * @return {DataSources} Most registries return the created instance, but this registry returns a reference to\n * itself (to support chaining)\n */\n add(namespace, item, override = false) {\n if (this._registry.has(namespace)) {\n throw new Error(`The namespace ${namespace} is already in use by another source`);\n }\n\n if (namespace.match(/[^A-Za-z0-9_]/)) {\n throw new Error(`Data source namespace names can only contain alphanumeric characters or underscores. Invalid name: ${namespace}`);\n }\n if (Array.isArray(item)) {\n const [type, options] = item;\n item = this._registry.create(type, options);\n }\n // Each datasource in the chain should be aware of its assigned namespace\n item.source_id = namespace;\n\n super.add(namespace, item, override);\n return this;\n }\n}\n\n\nexport default DataSources;\n","/**\n * Whether imported (ES6 modules) or loaded via script tag (UMD), this module represents\n * the \"public interface\" via which core LocusZoom features and plugins are exposed for programmatic usage.\n *\n * A library using this file will need to load `locuszoom.css` separately in order for styles to appear.\n *\n * @module LocusZoom\n */\nimport version from './version';\n\nimport {default as DataSources} from './data';\nimport { populate } from './helpers/display';\n\nimport {\n ADAPTERS as Adapters,\n DATA_LAYERS as DataLayers,\n DATA_OPS as DataFunctions,\n LAYOUTS as Layouts,\n MATCHERS as MatchFunctions,\n SCALABLE as ScaleFunctions,\n TRANSFORMS as TransformationFunctions,\n WIDGETS as Widgets,\n} from './registry';\n\n\nconst LocusZoom = {\n version,\n // Helpers for creating plots- the main public interface for most use cases\n populate,\n DataSources,\n // Registries for plugin system\n Adapters,\n DataLayers,\n DataFunctions,\n Layouts,\n MatchFunctions,\n ScaleFunctions,\n TransformationFunctions,\n Widgets,\n\n get KnownDataSources() { // Backwards- compatibility alias\n console.warn('Deprecation warning: KnownDataSources has been renamed to \"Adapters\"');\n return Adapters;\n },\n};\n\n\n/**\n * @callback pluginCallback\n * @param {Object} LocusZoom The global LocusZoom object\n * @param args Any additional arguments passed to LocusZoom.use will be passed to the function when the plugin is loaded\n */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n * @alias module:LocusZoom.use\n * @param {pluginCallback} plugin The plugin should be a module that exports the function as either the default export,\n * or as a member named \"install\"\n * @param args Additional options to be passed when creating the plugin\n */\nLocusZoom.use = function(plugin, ...args) {\n // Deliberately similar implementation to Vue.js .use() plugin system\n if (INSTALLED_PLUGINS.includes(plugin)) {\n // Avoid double-installation of a plugin\n return;\n }\n\n args.unshift(LocusZoom); // All plugins are passed a reference to LocusZoom object\n if (typeof plugin.install === 'function') {\n plugin.install.apply(plugin, args);\n } else if (typeof plugin === 'function') {\n plugin.apply(null, args);\n } else {\n throw new Error('Plugin must export a function that receives the LocusZoom object as an argument');\n }\n INSTALLED_PLUGINS.push(plugin);\n};\n\n\nexport default LocusZoom;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/esm/version.js b/esm/version.js index d56c7b1b..3a0c7fcc 100644 --- a/esm/version.js +++ b/esm/version.js @@ -1 +1 @@ -export default '0.14.0-beta.4'; +export default '0.14.0'; diff --git a/index.html b/index.html index 22320ce8..bf1b7bc6 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,7 @@

                                                                                                                                                                                                                                                                                Get LocusZoom.js

                                                                                                                                                                                                                                                                                CSS
                                                                                                                                                                                                                                                                                - https://cdn.jsdelivr.net/npm/locuszoom@0.14.0-beta.4/dist/locuszoom.css + https://cdn.jsdelivr.net/npm/locuszoom@0.14.0/dist/locuszoom.css
                                                                                                                                                                                                                                                                                All CSS classes are namespaced to avoid collisions. Use <link crossorigin="anonymous" ...> to ensure that saving images works correctly.
                                                                                                                                                                                                                                                                                @@ -96,7 +96,7 @@
                                                                                                                                                                                                                                                                                Dependencies
                                                                                                                                                                                                                                                                                Javascript
                                                                                                                                                                                                                                                                                diff --git a/package-lock.json b/package-lock.json index 117de224..065a70aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.14.0-beta.3", + "version": "0.14.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -4986,7 +4986,8 @@ "dependencies": { "ansi-regex": { "version": "5.0.0", - "resolved": "", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { @@ -7257,7 +7258,8 @@ "dependencies": { "ansi-regex": { "version": "5.0.0", - "resolved": "", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "is-fullwidth-code-point": { diff --git a/package.json b/package.json index 9288bafe..d83237ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.14.0-beta.4", + "version": "0.14.0", "main": "dist/locuszoom.app.min.js", "module": "esm/index.js", "sideEffects": true,